diff --git a/packages/survey-angular-ui/src/components/skeleton/skeleton.component.html b/packages/survey-angular-ui/src/components/skeleton/skeleton.component.html index eaee96122f..3ca253e438 100644 --- a/packages/survey-angular-ui/src/components/skeleton/skeleton.component.html +++ b/packages/survey-angular-ui/src/components/skeleton/skeleton.component.html @@ -1,3 +1,3 @@ -
+
\ No newline at end of file diff --git a/packages/survey-core/src/base.ts b/packages/survey-core/src/base.ts index cf7099469c..58be6aaebb 100644 --- a/packages/survey-core/src/base.ts +++ b/packages/survey-core/src/base.ts @@ -1171,6 +1171,57 @@ export class Base { } } +export class RenderingCompletedAwaiter { + constructor(private _elements: Array, 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 { constructor( public index: number, diff --git a/packages/survey-core/src/panel.ts b/packages/survey-core/src/panel.ts index 342487c695..e4e198c7bf 100644 --- a/packages/survey-core/src/panel.ts +++ b/packages/survey-core/src/panel.ts @@ -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, @@ -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); @@ -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; @@ -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; @@ -1323,11 +1333,22 @@ export class PanelModelBase extends SurveyElement 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); } @@ -1535,16 +1556,6 @@ export class PanelModelBase extends SurveyElement } 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 = this.getPage(this.parent); return !!page ? page.getDragDropInfo() : undefined; @@ -1573,25 +1584,66 @@ export class PanelModelBase extends SurveyElement } } } - 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 { + const allRows: Array = []; + this.rows.forEach(row => { + const nestedRows: Array = []; + row.elements.forEach(element => { + if (element.isPanel) { + nestedRows.push(...(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): { 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, 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; diff --git a/packages/survey-core/src/survey-element.ts b/packages/survey-core/src/survey-element.ts index 35d392ca91..d0917515a1 100644 --- a/packages/survey-core/src/survey-element.ts +++ b/packages/survey-core/src/survey-element.ts @@ -188,31 +188,43 @@ export class SurveyElement 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; } @@ -528,6 +540,13 @@ export class SurveyElement 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; @@ -695,7 +714,7 @@ export class SurveyElement 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 { @@ -1090,7 +1109,7 @@ export class SurveyElement 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); } @@ -1124,16 +1143,17 @@ export class SurveyElement 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}`); } } @@ -1149,8 +1169,8 @@ export class SurveyElement 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); @@ -1160,7 +1180,7 @@ export class SurveyElement 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); } } diff --git a/packages/survey-core/src/survey.ts b/packages/survey-core/src/survey.ts index 55a518ab4c..eea7fabba7 100644 --- a/packages/survey-core/src/survey.ts +++ b/packages/survey-core/src/survey.ts @@ -47,7 +47,7 @@ import { } from "./expressionItems"; import { ExpressionRunner, ConditionRunner } from "./conditions"; import { settings } from "./settings"; -import { isContainerVisible, isMobile, mergeValues, scrollElementByChildId, navigateToUrl, getRenderedStyleSize, getRenderedSize, wrapUrlForBackgroundImage, chooseFiles } from "./utils/utils"; +import { isContainerVisible, isMobile, mergeValues, activateLazyRenderingChecks, navigateToUrl, getRenderedStyleSize, getRenderedSize, wrapUrlForBackgroundImage, chooseFiles } from "./utils/utils"; import { SurveyError } from "./survey-error"; import { IAction, Action } from "./actions/action"; import { ActionContainer } from "./actions/container"; @@ -1300,22 +1300,23 @@ export class SurveyModel extends SurveyElementCore this.lazyRenderingFirstBatchSizeValue = val; } - public disableLazyRenderingBeforeElement(el: IElement): void { - if(this.isDesignMode) { - const page = this.getPageByElement(el); - const index = this.pages.indexOf(page); - for (let i = index; i >= 0; i--) { - const currentPage = this.pages[i]; - currentPage.disableLazyRenderingBeforeElement(currentPage == page ? el : undefined); - } - } + protected _isLazyRenderingSuspended = false; + public get isLazyRenderingSuspended(): boolean { + return this._isLazyRenderingSuspended; + } + protected suspendLazyRendering(): void { + if (!this.isLazyRendering) return; + this._isLazyRenderingSuspended = true; + } + protected releaseLazyRendering(): void { + if (!this.isLazyRendering) return; + this._isLazyRenderingSuspended = false; } - private updateLazyRenderingRowsOnRemovingElements() { if (!this.isLazyRendering) return; var page = this.currentPage; if (!!page) { - scrollElementByChildId(page.id); + activateLazyRenderingChecks(page.id); } } /** @@ -5264,11 +5265,12 @@ export class SurveyModel extends SurveyElementCore return options.actions; } + public skeletonHeight: number = undefined; + scrollElementToTop( - element: ISurveyElement, - question: Question, - page: PageModel, - id: string, scrollIfVisible?: boolean, scrollIntoViewOptions?: ScrollIntoViewOptions + element: ISurveyElement, question: Question, page: PageModel, + id: string, scrollIfVisible?: boolean, scrollIntoViewOptions?: ScrollIntoViewOptions, + passedRootElement?: HTMLElement ): any { const options: ScrollingElementToTopEvent = { element: element, @@ -5279,7 +5281,24 @@ export class SurveyModel extends SurveyElementCore }; this.onScrollingElementToTop.fire(this, options); if (!options.cancel) { - SurveyElement.ScrollElementToTop(options.elementId, scrollIfVisible, scrollIntoViewOptions); + const elementPage = this.getPageByElement(element as IElement); + if (this.isLazyRendering) { + let elementsToRenderBefore = 1; + const { rootElement } = settings.environment; + const surveyRootElement = this.rootElement || passedRootElement || rootElement as any; + if (!!this.skeletonHeight && !!surveyRootElement && typeof surveyRootElement.getBoundingClientRect === "function") { + elementsToRenderBefore = surveyRootElement.getBoundingClientRect().height / this.skeletonHeight - 1; + } + elementPage.forceRenderElement(element as IElement, () => { + this.suspendLazyRendering(); + SurveyElement.ScrollElementToTop(options.elementId, scrollIfVisible, scrollIntoViewOptions, () => { + this.releaseLazyRendering(); + activateLazyRenderingChecks(elementPage.id); + }); + }, elementsToRenderBefore); + } else { + SurveyElement.ScrollElementToTop(options.elementId, scrollIfVisible, scrollIntoViewOptions); + } } } diff --git a/packages/survey-core/src/utils/utils.ts b/packages/survey-core/src/utils/utils.ts index c1f5ad1c26..529b08cb1e 100644 --- a/packages/survey-core/src/utils/utils.ts +++ b/packages/survey-core/src/utils/utils.ts @@ -142,7 +142,7 @@ function findScrollableParent(element: HTMLElement): HTMLElement { return findScrollableParent(element.parentElement); } -function scrollElementByChildId(id: string) { +function activateLazyRenderingChecks(id: string): void { const environment: ISurveyEnvironment = settings.environment; if (!environment) return; const { root } = environment; @@ -781,7 +781,7 @@ export { getElement, isElementVisible, findScrollableParent, - scrollElementByChildId, + activateLazyRenderingChecks, navigateToUrl, wrapUrlForBackgroundImage, createSvg, diff --git a/packages/survey-core/tests/paneltests.ts b/packages/survey-core/tests/paneltests.ts index eb8b038bc6..1d54d14304 100644 --- a/packages/survey-core/tests/paneltests.ts +++ b/packages/survey-core/tests/paneltests.ts @@ -2866,4 +2866,509 @@ QUnit.test("Panel hasTextInTitle - reactive property #8816", function (assert) { panel2.title = ""; assert.equal(panel2.hasTextInTitle, false, "panel2 #2, hasTextInTitle"); assert.equal(panel2.hasTitle, false, "panel2 #2, hasTitle"); +}); + +QUnit.test("row.isNeedRender for panels", function (assert) { + const json = { + pages: [ + { + name: "page1", + elements: [ + { + "type": "panel", + "name": "panel1", + "elements": [ + { + type: "text", + name: "q1", + }, + { + type: "text", + name: "q2", + }, + + ] + }, + { + type: "text", + name: "q3", + }, + ], + } + ], + }; + + const prevStartRowInLazyRendering = settings.lazyRowsRenderingStartRow; + settings.lazyRowsRenderingStartRow = 0; + try { + const survey = new SurveyModel(json); + survey.lazyRendering = true; + const page1: PageModel = survey.pages[0]; + assert.equal(page1.rows.length, 2, "There are 2 rows"); + assert.equal(page1.rows[0].isNeedRender, true, "isNeedRender page1 rows[0]"); + assert.equal(page1.rows[1].isNeedRender, false, "isNeedRender page1 rows[1]"); + + const panel1 = survey.pages[0].elements[0] as PanelModel; + assert.equal(panel1.rows.length, 2, "There are 2 rows in panel1"); + assert.equal(panel1.rows[0].isNeedRender, false, "isNeedRender panel1 rows[0]"); + assert.equal(panel1.rows[1].isNeedRender, false, "isNeedRender panel1 rows[1]"); + } finally { + settings.lazyRowsRenderingStartRow = prevStartRowInLazyRendering; + } +}); +QUnit.test("getAllRows for page", function (assert) { + const json = { + pages: [ + { + name: "page1", + elements: [ + { + "type": "panel", + "name": "panel1", + "elements": [ + { + type: "text", + name: "q1", + }, + { + type: "text", + name: "q2", + }, + + ] + }, + { + type: "text", + name: "q3", + }, + ], + } + ], + }; + + const prevStartRowInLazyRendering = settings.lazyRowsRenderingStartRow; + settings.lazyRowsRenderingStartRow = 0; + try { + const survey = new SurveyModel(json); + survey.lazyRendering = true; + const page1: PageModel = survey.pages[0]; + const allPageRows = page1.getAllRows(); + + assert.equal(allPageRows.length, 4, "There are 4 rows"); + assert.equal(allPageRows[0].elements[0].name, "panel1"); + assert.equal(allPageRows[1].elements[0].name, "q1"); + assert.equal(allPageRows[2].elements[0].name, "q2"); + assert.equal(allPageRows[3].elements[0].name, "q3"); + } finally { + settings.lazyRowsRenderingStartRow = prevStartRowInLazyRendering; + } +}); +QUnit.test("forceRenderRows for page", async function (assert) { + let done = assert.async(1); + const json = { + pages: [ + { + name: "page1", + elements: [ + { + "type": "panel", + "name": "panel1", + "elements": [ + { + type: "text", + name: "q1", + }, + { + type: "text", + name: "q2", + }, + + ] + }, + { + type: "text", + name: "q3", + }, + ], + } + ], + }; + + const prevStartRowInLazyRendering = settings.lazyRowsRenderingStartRow; + settings.lazyRowsRenderingStartRow = 0; + try { + const survey = new SurveyModel(json); + survey.lazyRendering = true; + survey.getAllQuestions().forEach(q => { + q.supportOnElementRenderedEvent = true; + q.onElementRenderedEventEnabled = true; + }); + const page1: PageModel = survey.pages[0]; + const allPageRows = page1.getAllRows(); + assert.equal(allPageRows[0].isNeedRender, true, "isNeedRender for panel"); + assert.equal(allPageRows[1].isNeedRender, false, "isNeedRender for q1"); + assert.equal(allPageRows[2].isNeedRender, false, "isNeedRender for q2"); + assert.equal(allPageRows[3].isNeedRender, false, "isNeedRender for q3"); + + page1.forceRenderRows([allPageRows[2], allPageRows[3]], () => { + settings.lazyRowsRenderingStartRow = prevStartRowInLazyRendering; + assert.equal(allPageRows[0].isNeedRender, true, "isNeedRender for panel"); + assert.equal(allPageRows[1].isNeedRender, false, "isNeedRender for q1"); + assert.equal(allPageRows[2].isNeedRender, true, "isNeedRender for q2"); + assert.equal(allPageRows[3].isNeedRender, true, "isNeedRender for q3"); + done(); + }); + survey.getQuestionByName("q2").onElementRerendered.fire({} as any, {}); + survey.getQuestionByName("q3").onElementRerendered.fire({} as any, {}); + } finally { + settings.lazyRowsRenderingStartRow = prevStartRowInLazyRendering; + } +}); +QUnit.test("forceRenderElement for page the exact element, gap = 0", async function (assert) { + let done = assert.async(1); + const json = { + pages: [ + { + name: "page1", + elements: [ + { + "type": "panel", + "name": "panel1", + "elements": [ + { + type: "text", + name: "q1", + }, + { + type: "text", + name: "q2", + }, + + ] + }, + { + type: "text", + name: "q3", + }, + ], + } + ], + }; + + const prevStartRowInLazyRendering = settings.lazyRowsRenderingStartRow; + settings.lazyRowsRenderingStartRow = 0; + try { + const survey = new SurveyModel(json); + survey.lazyRendering = true; + survey.getAllQuestions().forEach(q => { + q.supportOnElementRenderedEvent = true; + q.onElementRenderedEventEnabled = true; + }); + const page1: PageModel = survey.pages[0]; + const allPageRows = page1.getAllRows(); + assert.equal(allPageRows[0].isNeedRender, true, "isNeedRender for panel"); + assert.equal(allPageRows[1].isNeedRender, false, "isNeedRender for q1"); + assert.equal(allPageRows[2].isNeedRender, false, "isNeedRender for q2"); + assert.equal(allPageRows[3].isNeedRender, false, "isNeedRender for q3"); + + page1.forceRenderElement(survey.getQuestionByName("q3"), () => { + settings.lazyRowsRenderingStartRow = prevStartRowInLazyRendering; + assert.equal(allPageRows[0].isNeedRender, true, "isNeedRender for panel"); + assert.equal(allPageRows[1].isNeedRender, false, "isNeedRender for q1"); + assert.equal(allPageRows[2].isNeedRender, false, "isNeedRender for q2"); + assert.equal(allPageRows[3].isNeedRender, true, "isNeedRender for q3"); + done(); + }); + survey.getQuestionByName("q3").onElementRerendered.fire({} as any, {}); + } finally { + settings.lazyRowsRenderingStartRow = prevStartRowInLazyRendering; + } +}); +QUnit.test("forceRenderElement for page with one prev element, gap = 1", async function (assert) { + let done = assert.async(1); + const json = { + pages: [ + { + name: "page1", + elements: [ + { + "type": "panel", + "name": "panel1", + "elements": [ + { + type: "text", + name: "q1", + }, + { + type: "text", + name: "q2", + }, + + ] + }, + { + type: "text", + name: "q3", + }, + ], + } + ], + }; + + const prevStartRowInLazyRendering = settings.lazyRowsRenderingStartRow; + settings.lazyRowsRenderingStartRow = 0; + try { + const survey = new SurveyModel(json); + survey.lazyRendering = true; + survey.getAllQuestions().forEach(q => { + q.supportOnElementRenderedEvent = true; + q.onElementRenderedEventEnabled = true; + }); + const page1: PageModel = survey.pages[0]; + const allPageRows = page1.getAllRows(); + assert.equal(allPageRows[0].isNeedRender, true, "isNeedRender for panel"); + assert.equal(allPageRows[1].isNeedRender, false, "isNeedRender for q1"); + assert.equal(allPageRows[2].isNeedRender, false, "isNeedRender for q2"); + assert.equal(allPageRows[3].isNeedRender, false, "isNeedRender for q3"); + + page1.forceRenderElement(survey.getQuestionByName("q3"), () => { + settings.lazyRowsRenderingStartRow = prevStartRowInLazyRendering; + assert.equal(allPageRows[0].isNeedRender, true, "isNeedRender for panel"); + assert.equal(allPageRows[1].isNeedRender, false, "isNeedRender for q1"); + assert.equal(allPageRows[2].isNeedRender, true, "isNeedRender for q2"); + assert.equal(allPageRows[3].isNeedRender, true, "isNeedRender for q3"); + done(); + }, 1); + survey.getQuestionByName("q2").onElementRerendered.fire({} as any, {}); + survey.getQuestionByName("q3").onElementRerendered.fire({} as any, {}); + } finally { + settings.lazyRowsRenderingStartRow = prevStartRowInLazyRendering; + } +}); +QUnit.test("row.isNeedRender for nested panels", function (assert) { + const json = { + pages: [ + { + name: "page1", + elements: [ + { + "type": "panel", + "name": "panel1", + "elements": [ + { + "type": "panel", + "name": "panel2", + "elements": [ + { + "type": "panel", + "name": "panel3", + "elements": [ + { + type: "text", + name: "q1", + }, + { + type: "text", + name: "q2", + }, + + ] + }, + ] + }, + { + type: "text", + name: "q3", + }, + ] + }, + { + type: "text", + name: "q4", + }, + ], + } + ], + }; + + const prevStartRowInLazyRendering = settings.lazyRowsRenderingStartRow; + settings.lazyRowsRenderingStartRow = 0; + try { + const survey = new SurveyModel(json); + survey.lazyRendering = true; + const page1: PageModel = survey.pages[0]; + const allPageRows = page1.getAllRows(); + assert.equal(allPageRows.length, 7, "7 rows with panels"); + assert.equal(allPageRows[0].isNeedRender, true, "isNeedRender for panel1"); + assert.equal(allPageRows[1].isNeedRender, true, "isNeedRender for panel2"); + assert.equal(allPageRows[2].isNeedRender, true, "isNeedRender for panel3"); + assert.equal(allPageRows[3].isNeedRender, false, "isNeedRender for q1"); + assert.equal(allPageRows[4].isNeedRender, false, "isNeedRender for q2"); + assert.equal(allPageRows[5].isNeedRender, false, "isNeedRender for q3"); + assert.equal(allPageRows[6].isNeedRender, false, "isNeedRender for q4"); + } finally { + settings.lazyRowsRenderingStartRow = prevStartRowInLazyRendering; + } +}); +QUnit.test("row.isNeedRender for nested panels - complex", function (assert) { + const json = { + pages: [ + { + name: "page1", + elements: [ + { + "type": "panel", + "name": "panel1", + "elements": [ + { + "type": "paneldynamic", + "name": "panel2", + "templateElements": [ + { + "type": "text", + "name": "q1", + }, + { + "type": "text", + "name": "q2", + "startWithNewLine": false, + }, + { + "type": "text", + "name": "q3", + }, + { + "type": "text", + "name": "q4", + "startWithNewLine": false, + }, + ], + "templateTitle": "item #{panelIndex}", + "panelCount": 1, + "minPanelCount": 1, + "keyName": "name", + }, + { + "type": "panel", + "name": "panel3", + "title": "Totals", + "elements": [ + { + "type": "expression", + "name": "q5", + }, + { + "type": "expression", + "name": "q6", + "startWithNewLine": false, + } + ] + } + ] + }, + { + "type": "text", + "name": "last-question" + } + ], + } + ], + }; + + const prevStartRowInLazyRendering = settings.lazyRowsRenderingStartRow; + settings.lazyRowsRenderingStartRow = 0; + try { + const survey = new SurveyModel(json); + survey.lazyRendering = true; + const page1: PageModel = survey.pages[0]; + const allPageRows = page1.getAllRows(); + assert.equal(allPageRows.length, 7, "7 rows with panels"); + assert.equal(allPageRows[0].elements[0].name, "panel1"); + assert.equal(allPageRows[0].isNeedRender, true, "isNeedRender for panel1"); + assert.equal(allPageRows[1].elements[0].name, "panel2", "panel2 - paneldynamic"); + assert.equal(allPageRows[1].isNeedRender, true, "isNeedRender for panel2"); + assert.equal(allPageRows[2].elements[0].name, "q1"); + // assert.equal(allPageRows[2].isNeedRender, false, "isNeedRender for q1, q2"); + assert.equal(allPageRows[3].elements[0].name, "q3"); + // assert.equal(allPageRows[3].isNeedRender, false, "isNeedRender for q3, q4"); + assert.equal(allPageRows[4].elements[0].name, "panel3"); + assert.equal(allPageRows[4].isNeedRender, true, "isNeedRender for panel3"); + assert.equal(allPageRows[5].elements[0].name, "q5"); + assert.equal(allPageRows[5].isNeedRender, false, "isNeedRender for q5, q6"); + assert.equal(allPageRows[6].elements[0].name, "last-question"); + assert.equal(allPageRows[6].isNeedRender, false, "isNeedRender for q-last"); + } finally { + settings.lazyRowsRenderingStartRow = prevStartRowInLazyRendering; + } +}); +QUnit.test("row.isNeedRender panel dynamic different modes - ordinary and designer", function (assert) { + const json = { + pages: [ + { + name: "page1", + elements: [ + { + "type": "paneldynamic", + "name": "panel1", + "templateElements": [ + { + "type": "text", + "name": "q1", + }, + { + "type": "text", + "name": "q2", + "startWithNewLine": false, + }, + { + "type": "text", + "name": "q3", + }, + { + "type": "text", + "name": "q4", + "startWithNewLine": false, + }, + ], + "templateTitle": "item #{panelIndex}", + "panelCount": 2, + "minPanelCount": 1, + }, + ], + } + ], + }; + + const prevStartRowInLazyRendering = settings.lazyRowsRenderingStartRow; + settings.lazyRowsRenderingStartRow = 0; + try { + const survey = new SurveyModel(json); + survey.lazyRendering = true; + const page1: PageModel = survey.pages[0]; + + let allPageRows = page1.getAllRows(); + assert.equal(allPageRows.length, 5, "7 rows with inner panels in runtime mode"); + assert.equal(allPageRows[0].elements[0].name, "panel1"); + assert.equal(allPageRows[0].isNeedRender, true, "isNeedRender for panel1"); + assert.equal(allPageRows[1].elements[0].name, "q1"); + // assert.equal(allPageRows[1].isNeedRender, false, "isNeedRender for q1, q2"); + assert.equal(allPageRows[2].elements[0].name, "q3"); + // assert.equal(allPageRows[2].isNeedRender, false, "isNeedRender for q3, q4"); + assert.equal(allPageRows[3].elements[0].name, "q1"); + // assert.equal(allPageRows[3].isNeedRender, false, "isNeedRender for q1, q2"); + assert.equal(allPageRows[4].elements[0].name, "q3"); + // assert.equal(allPageRows[4].isNeedRender, false, "isNeedRender for q3, q4"); + + survey.setDesignMode(true); + allPageRows = page1.getAllRows(); + assert.equal(allPageRows.length, 3, "3 rows with inner panels in design mode"); + assert.equal(allPageRows[0].elements[0].name, "panel1"); + assert.equal(allPageRows[0].isNeedRender, true, "isNeedRender for panel1"); + assert.equal(allPageRows[1].elements[0].name, "q1"); + // assert.equal(allPageRows[1].isNeedRender, false, "isNeedRender for q1, q2"); + assert.equal(allPageRows[2].elements[0].name, "q3"); + // assert.equal(allPageRows[2].isNeedRender, false, "isNeedRender for q3, q4"); + } finally { + settings.lazyRowsRenderingStartRow = prevStartRowInLazyRendering; + } }); \ No newline at end of file diff --git a/packages/survey-core/tests/surveytests.ts b/packages/survey-core/tests/surveytests.ts index e341cc1d01..88945b76f7 100644 --- a/packages/survey-core/tests/surveytests.ts +++ b/packages/survey-core/tests/surveytests.ts @@ -20488,84 +20488,3 @@ QUnit.test("Trim key in setting the data, Bug#8586", function (assert) { assert.equal(survey.getQuestionByName("q3").value, "c", "q3.value"); assert.equal(survey.getQuestionByName("q4").value, "d", "q3.value"); }); - -QUnit.test("Check disableLazyRenderingBeforeElement method", (assert) => { - const survey = new SurveyModel({ - pages: [ - { - name: "page1", - elements: [ - { - type: "text", - name: "q1" - } - ] - }, - { - name: "page2", - elements: [ - { - type: "text", - name: "q2" - }, - { - type: "text", - name: "q3" - }, - { - type: "text", - name: "q4" - }] - }, - { - name: "page3", - elements: [ - { - type: "text", - name: "q5" - } - ] - } - ] - }); - survey.setDesignMode(true); - const question = survey.getQuestionByName("q3"); - const page1 = survey.getPageByName("page1"); - const page2 = survey.getPageByName("page2"); - const page3 = survey.getPageByName("page3"); - - [page1, page2, page3].forEach(page => { - page.onFirstRendering(); - page.rows.forEach(row => row.isNeedRender = false); - } - ); - - assert.notOk(page1.rows[0].isNeedRender); - - assert.notOk(page2.rows[0].isNeedRender); - assert.notOk(page2.rows[1].isNeedRender); - assert.notOk(page2.rows[2].isNeedRender); - - assert.notOk(page3.rows[0].isNeedRender); - - survey.disableLazyRenderingBeforeElement(question); - - assert.ok(page1.rows[0].isNeedRender); - - assert.ok(page2.rows[0].isNeedRender); - assert.ok(page2.rows[1].isNeedRender); - assert.notOk(page2.rows[2].isNeedRender); - - assert.notOk(page3.rows[0].isNeedRender); - - page2.rows[0].isNeedRender = false; - survey.disableLazyRenderingBeforeElement(question); - - assert.ok(page1.rows[0].isNeedRender); - - assert.notOk(page2.rows[0].isNeedRender); - assert.ok(page2.rows[1].isNeedRender); - assert.notOk(page2.rows[2].isNeedRender); - - assert.notOk(page3.rows[0].isNeedRender); -}); \ No newline at end of file diff --git a/packages/survey-react-ui/src/components/skeleton.tsx b/packages/survey-react-ui/src/components/skeleton.tsx index ff293fb79c..01ca05f89f 100644 --- a/packages/survey-react-ui/src/components/skeleton.tsx +++ b/packages/survey-react-ui/src/components/skeleton.tsx @@ -2,9 +2,9 @@ import React from "react"; import { ReactElementFactory } from "../element-factory"; export class Skeleton extends React.Component { - render() { + render(): JSX.Element { return ( -
+
); } diff --git a/packages/survey-vue3-ui/src/components/Skeleton.vue b/packages/survey-vue3-ui/src/components/Skeleton.vue index 7cff341841..8a61978017 100644 --- a/packages/survey-vue3-ui/src/components/Skeleton.vue +++ b/packages/survey-vue3-ui/src/components/Skeleton.vue @@ -1,5 +1,5 @@