diff --git a/packages/survey-core/src/base-interfaces.ts b/packages/survey-core/src/base-interfaces.ts index 9019cde4fe..c135d71e14 100644 --- a/packages/survey-core/src/base-interfaces.ts +++ b/packages/survey-core/src/base-interfaces.ts @@ -136,6 +136,7 @@ export interface ISurvey extends ITextProcessor, ISurveyErrorOwner { questionTitlePattern: string; getUpdatedQuestionTitle(question: IQuestion, title: string): string; getUpdatedQuestionNo(question: IQuestion, no: string): string; + getUpdatedPanelNo(question: IPanel, no: string): string; getUpdatedPageNo(question: IPage, no: string): string; getUpdatedElementTitleActions( element: ISurveyElement, diff --git a/packages/survey-core/src/panel.ts b/packages/survey-core/src/panel.ts index b49e11b117..7ae4182dfe 100644 --- a/packages/survey-core/src/panel.ts +++ b/packages/survey-core/src/panel.ts @@ -2187,13 +2187,14 @@ export class PanelModel extends PanelModelBase implements IElement { * @see visibleIf */ public get no(): string { - return this.getPropertyValue("no", ""); + return this.getPropertyValue("no", undefined, () => this.calcNo()); } - protected setNo(visibleIndex: number): void { - this.setPropertyValue( - "no", - Helpers.getNumberByIndex(this.visibleIndex, this.getStartIndex()) - ); + private calcNo(): string { + let no = Helpers.getNumberByIndex(this.visibleIndex, this.getStartIndex()); + if(this.survey) { + no = this.survey.getUpdatedPanelNo(this, no); + } + return no || ""; } protected notifyStateChanged(prevState: string): void { if(!this.isLoadingFromJson) { @@ -2218,7 +2219,7 @@ export class PanelModel extends PanelModelBase implements IElement { visibleIndex = index; } this.setPropertyValue("visibleIndex", visibleIndex); - this.setNo(visibleIndex); + this.resetPropertyValue("no"); return visibleIndex < 0 ? 0 : 1; } protected getPanelStartIndex(index: number): number { diff --git a/packages/survey-core/src/survey-events-api.ts b/packages/survey-core/src/survey-events-api.ts index a1e15f107f..7aa2c2730a 100644 --- a/packages/survey-core/src/survey-events-api.ts +++ b/packages/survey-core/src/survey-events-api.ts @@ -414,6 +414,12 @@ export interface GetPageNumberEvent extends PageEventMixin { */ number: string; } +export interface GetPanelNumberEvent extends PanelEventMixin { + /** + * A panel number. Note that this is a string value that contains not only the number itself but also the characters that separate the number from the panel title: `"1. "`, `"2. "`, etc. You can change this parameter's value. + */ + number: string; +} export interface GetProgressTextEvent { /** * The number of questions with input fields. [Image](https://surveyjs.io/form-library/examples/add-image-and-video-to-survey/), [HTML](https://surveyjs.io/form-library/examples/questiontype-html/), and [Expression](https://surveyjs.io/form-library/examples/questiontype-expression/) questions are not counted. diff --git a/packages/survey-core/src/survey.ts b/packages/survey-core/src/survey.ts index 4ea2f69b33..d284dc6e1c 100644 --- a/packages/survey-core/src/survey.ts +++ b/packages/survey-core/src/survey.ts @@ -58,7 +58,7 @@ import { TriggerExecutedEvent, CompletingEvent, CompleteEvent, ShowingPreviewEvent, NavigateToUrlEvent, CurrentPageChangingEvent, CurrentPageChangedEvent, ValueChangingEvent, ValueChangedEvent, VariableChangedEvent, QuestionVisibleChangedEvent, PageVisibleChangedEvent, PanelVisibleChangedEvent, QuestionCreatedEvent, QuestionAddedEvent, QuestionRemovedEvent, PanelAddedEvent, PanelRemovedEvent, PageAddedEvent, ValidateQuestionEvent, SettingQuestionErrorsEvent, ValidatePanelEvent, - ErrorCustomTextEvent, ValidatedErrorsOnCurrentPageEvent, ProcessHtmlEvent, GetQuestionTitleEvent, GetTitleTagNameEvent, GetQuestionNumberEvent, GetPageNumberEvent, GetProgressTextEvent, + ErrorCustomTextEvent, ValidatedErrorsOnCurrentPageEvent, ProcessHtmlEvent, GetQuestionTitleEvent, GetTitleTagNameEvent, GetQuestionNumberEvent, GetPageNumberEvent, GetPanelNumberEvent, GetProgressTextEvent, TextMarkdownEvent, TextRenderAsEvent, SendResultEvent, GetResultEvent, UploadFilesEvent, DownloadFileEvent, ClearFilesEvent, LoadChoicesFromServerEvent, ProcessTextValueEvent, UpdateQuestionCssClassesEvent, UpdatePanelCssClassesEvent, UpdatePageCssClassesEvent, UpdateChoiceItemCssEvent, AfterRenderSurveyEvent, AfterRenderPageEvent, AfterRenderQuestionEvent, AfterRenderQuestionInputEvent, AfterRenderPanelEvent, FocusInQuestionEvent, FocusInPanelEvent, @@ -429,6 +429,12 @@ export class SurveyModel extends SurveyElementCore * @deprecated */ public onGetQuestionNo: EventBase = this.onGetQuestionNumber; + /** + * An event that is raised before the survey calculates a panel number. Handle this event to modify panel numbers. + * + * This event is raised only for the panels with a [specified title](https://surveyjs.io/form-library/documentation/api-reference/panel-model#title) and [visible number](https://surveyjs.io/form-library/documentation/api-reference/panel-model#showNumber). + */ + public onGetPanelNumber: EventBase = this.addEvent(); /** * An event that is raised before the survey calculates a page number. Handle this event to modify page numbers. * @@ -987,6 +993,9 @@ export class SurveyModel extends SurveyElementCore this.onGetQuestionNumber.onCallbacksChanged = () => { this.resetVisibleIndexes(); }; + this.onGetPanelNumber.onCallbacksChanged = () => { + this.resetVisibleIndexes(); + }; this.onGetProgressText.onCallbacksChanged = () => { this.updateProgressText(); }; @@ -2719,6 +2728,12 @@ export class SurveyModel extends SurveyElementCore this.onGetQuestionNumber.fire(this, options); return options.no === no ? options.number : options.no; } + getUpdatedPanelNo(panel: PanelModel, no: string): string { + if (this.onGetPanelNumber.isEmpty) return no; + const options: GetPanelNumberEvent = { panel: panel, number: no }; + this.onGetPanelNumber.fire(this, options); + return options.number; + } getUpdatedPageNo(page: PageModel, no: string): string { if (this.onGetPageNumber.isEmpty) return no; const options: GetPageNumberEvent = { page: page, number: no }; diff --git a/packages/survey-core/tests/paneltests.ts b/packages/survey-core/tests/paneltests.ts index eb075b72ef..d20570d85d 100644 --- a/packages/survey-core/tests/paneltests.ts +++ b/packages/survey-core/tests/paneltests.ts @@ -3397,4 +3397,56 @@ QUnit.test("Nested pages", function (assert) { assert.equal(page1.getTemplate(), "page", "template #3"); assert.equal(page1.survey.state, "running", "survey state #3"); assert.equal(page1.isDisposed, false, "The page is not disposed"); -}); \ No newline at end of file +}); +QUnit.test("survey.onGetPanelNumber", function (assert) { + const survey = new SurveyModel({ + elements: [ + { + type: "panel", name: "panel1", title: "Panel 1", + showNumber: true, showQuestionNumbers: "onpanel", + elements: [ + { type: "panel", name: "panel2", + showNumber: true, showQuestionNumbers: "onpanel", title: "Panel 2", + elements: [ + { type: "text", name: "q1" }, + { type: "text", name: "q2" } + ] + }, + { type: "text", name: "q3" }, + { type: "text", name: "q4" } + ] + }, + { + type: "panel", name: "panel3", + showNumber: true, showQuestionNumbers: "onpanel", title: "Panel 3", + elements: [ + { type: "text", name: "q5" }, + { type: "text", name: "q6" } + ] + }, + { type: "text", name: "q7" } + ] + }); + survey.onGetQuestionNumber.add((sender, options) => { + const parent: any = options.question.parent; + if(!!parent && parent.no) { + options.number = parent.no + options.number; + } + }); + survey.onGetPanelNumber.add((sender, options) => { + const parent: any = options.panel.parent; + if(!!parent && parent.no) { + options.number = parent.no + options.number; + } + }); + assert.equal(survey.getPanelByName("panel1").no, "1.", "panel1.no"); + assert.equal(survey.getPanelByName("panel2").no, "1.1.", "panel2.no"); + assert.equal(survey.getPanelByName("panel3").no, "2.", "panel3.no"); + assert.equal(survey.getQuestionByName("q1").no, "1.1.1.", "q1.no"); + assert.equal(survey.getQuestionByName("q2").no, "1.1.2.", "q2.no"); + assert.equal(survey.getQuestionByName("q3").no, "1.2.", "q3.no"); + assert.equal(survey.getQuestionByName("q4").no, "1.3.", "q4.no"); + assert.equal(survey.getQuestionByName("q5").no, "2.1.", "q5.no"); + assert.equal(survey.getQuestionByName("q6").no, "2.2.", "q6.no"); + assert.equal(survey.getQuestionByName("q7").no, "3.", "q7.no"); +});