diff --git a/src/question.ts b/src/question.ts index 8890c29cc8..b2780e05df 100644 --- a/src/question.ts +++ b/src/question.ts @@ -1733,10 +1733,6 @@ export class Question extends SurveyElement } return res; } - private addSupportedValidators( - supportedValidators: Array, - classValidators: Array - ) { } public addConditionObjectsByContext(objects: Array, context: any): void { objects.push({ name: this.getValueName(), @@ -1744,6 +1740,24 @@ export class Question extends SurveyElement question: this, }); } + /** + * Returns an array of questions nested within the current question. Use this method to obtain questions within [Multiple Text](https://surveyjs.io/form-library/documentation/api-reference/multiple-text-entry-question-model), [Dynamic Panel](https://surveyjs.io/form-library/documentation/api-reference/dynamic-panel-model), and [Matrix](https://surveyjs.io/form-library/documentation/api-reference/matrix-table-question-model)-like questions. + * @param visibleOnly A Boolean value that specifies whether to include only visible nested questions. + * @returns An array of nested questions. + */ + public getNestedQuestions(visibleOnly: boolean = false): Array { + const res: Array = []; + this.collectNestedQuestions(res, visibleOnly); + if(res.length === 1 && res[0] === this) return []; + return res; + } + public collectNestedQuestions(questions: Array, visibleOnly: boolean = false): void { + if(visibleOnly && !this.isVisible) return; + this.collectNestedQuestionsCore(questions, visibleOnly); + } + protected collectNestedQuestionsCore(questions: Array, visibleOnly: boolean): void { + questions.push(this); + } public getConditionJson(operator: string = null, path: string = null): any { var json = new JsonObject().toJsonObject(this); json["type"] = this.getType(); diff --git a/src/question_custom.ts b/src/question_custom.ts index f0cc537904..f442d03bd8 100644 --- a/src/question_custom.ts +++ b/src/question_custom.ts @@ -976,6 +976,10 @@ export class QuestionCompositeModel extends QuestionCustomModelBase { }); } } + protected collectNestedQuestionsCore(questions: Question[], visibleOnly: boolean): void { + if (!this.contentPanel) return; + this.contentPanel.questions.forEach(q => q.collectNestedQuestions(questions, visibleOnly)); + } protected convertDataValue(name: string, newValue: any): any { var val = this.getValueForContentPanel(this.value); if (!val) val = {}; diff --git a/src/question_matrixdropdownbase.ts b/src/question_matrixdropdownbase.ts index 5d8656e35a..3ff5ef5a84 100644 --- a/src/question_matrixdropdownbase.ts +++ b/src/question_matrixdropdownbase.ts @@ -1748,6 +1748,12 @@ export class QuestionMatrixDropdownModelBase extends QuestionMatrixBaseModel { + row.questions.forEach(q => q.collectNestedQuestions(questions, visibleOnly)); + }); + } protected getConditionObjectRowName(index: number): string { return ""; } diff --git a/src/question_multipletext.ts b/src/question_multipletext.ts index fbff5d3a43..bc993fea03 100644 --- a/src/question_multipletext.ts +++ b/src/question_multipletext.ts @@ -428,6 +428,9 @@ export class QuestionMultipleTextModel extends Question }); } } + protected collectNestedQuestionsCore(questions: Question[], visibleOnly: boolean): void { + this.items.forEach(item => item.editor.collectNestedQuestions(questions, visibleOnly)); + } public getConditionJson(operator: string = null, path: string = null): any { if (!path) return super.getConditionJson(); var item = this.getItemByName(path); diff --git a/src/question_paneldynamic.ts b/src/question_paneldynamic.ts index 215522b3fb..b8ba962b03 100644 --- a/src/question_paneldynamic.ts +++ b/src/question_paneldynamic.ts @@ -1411,6 +1411,13 @@ export class QuestionPanelDynamicModel extends Question } } } + protected collectNestedQuestionsCore(questions: Question[], visibleOnly: boolean): void { + const panels = visibleOnly ? this.visiblePanels : this.panels; + if(!Array.isArray(panels)) return; + panels.forEach(panel => { + panel.questions.forEach(q => q.collectNestedQuestions(questions, visibleOnly)); + }); + } public getConditionJson(operator: string = null, path: string = null): any { if (!path) return super.getConditionJson(operator, path); var questionName = path; diff --git a/src/survey.ts b/src/survey.ts index afacf21183..942ce52ae1 100644 --- a/src/survey.ts +++ b/src/survey.ts @@ -5181,22 +5181,31 @@ export class SurveyModel extends SurveyElementCore return result; } /** - * Returns a list of all questions in a survey. - * @param visibleOnly set it `true`, if you want to get only visible questions + * Returns a list of all questions in the survey. + * @param visibleOnly A Boolean value that specifies whether to include only visible questions. + * @param includeDesignTime For internal use. + * @param includeNested A Boolean value that specifies whether to include nested questions, such as questions within matrix cells. */ public getAllQuestions( visibleOnly: boolean = false, - includingDesignTime: boolean = false + includeDesignTime: boolean = false, + includeNested: boolean = false ): Array { - var result = new Array(); + var res: Array = []; for (var i: number = 0; i < this.pages.length; i++) { this.pages[i].addQuestionsToList( - result, + res, visibleOnly, - includingDesignTime + includeDesignTime ); } - return result; + if(!includeNested) return res; + const res2: Array = []; + res.forEach(q => { + res2.push(q); + q.getNestedQuestions(visibleOnly).forEach(nQ => res2.push(nQ)); + }); + return res2; } /** * Returns quiz questions. All visible questions that has input(s) widgets. @@ -5241,11 +5250,11 @@ export class SurveyModel extends SurveyElementCore */ public getAllPanels( visibleOnly: boolean = false, - includingDesignTime: boolean = false + includeDesignTime: boolean = false ): Array { var result = new Array(); for (var i: number = 0; i < this.pages.length; i++) { - this.pages[i].addPanelsIntoList(result, visibleOnly, includingDesignTime); + this.pages[i].addPanelsIntoList(result, visibleOnly, includeDesignTime); } return result; } diff --git a/tests/question_customtests.ts b/tests/question_customtests.ts index 39db27d4ba..7da15562bc 100644 --- a/tests/question_customtests.ts +++ b/tests/question_customtests.ts @@ -1427,6 +1427,28 @@ QUnit.test("Composite: addConditionObjectsByContext", function (assert) { ); ComponentCollection.Instance.clear(); }); +QUnit.test("Composite: getNestedQuestions", function (assert) { + var json = { + name: "testquestion", + elementsJSON: [ + { type: "text", name: "q1" }, + { + type: "dropdown", + name: "q2" + }, + ], + }; + ComponentCollection.Instance.add(json); + var survey = new SurveyModel({ + elements: [{ type: "testquestion", name: "cp_question" }], + }); + const q = survey.getAllQuestions()[0]; + const questions = q.getNestedQuestions(); + assert.equal(questions.length, 2, "#1"); + assert.equal(questions[0].name, "q1", "#2"); + assert.equal(questions[1].name, "q2", "#3"); + ComponentCollection.Instance.clear(); +}); QUnit.test("Composite: visibleIf and showPreview, Bug#2674", function (assert) { ComponentCollection.Instance.add({ name: "fullname", diff --git a/tests/question_matrixdynamictests.ts b/tests/question_matrixdynamictests.ts index 1a251bba6b..d1588b9169 100644 --- a/tests/question_matrixdynamictests.ts +++ b/tests/question_matrixdynamictests.ts @@ -1596,6 +1596,32 @@ QUnit.test("matrixDynamic.addConditionObjectsByContext", function (assert) { "addConditionObjectsByContext work correctly for matrix dynamic with context equals true" ); }); +QUnit.test("matrixDynamic.getNestedQuestions", function (assert) { + const survey = new SurveyModel({ + elements: [ + { type: "matrixdynamic", name: "matrix", rowCount: 2, + columns: [{ name: "col1", visibleIf: "{row.col2} = 'a'" }, { name: "col2" }] + } + ] + }); + const q = survey.getQuestionByName("matrix"); + let questions = q.getNestedQuestions(); + assert.equal(questions.length, 4, "4 cells"); + assert.equal(questions[0].name, "col1", "cells[0, 0]"); + assert.equal(questions[1].name, "col2", "cells[0, 1]"); + assert.equal(questions[0].name, "col1", "cells[1, 0]"); + assert.equal(questions[1].name, "col2", "cells[1, 1]"); + + const rows = q.visibleRows; + rows[1].getQuestionByColumnName("col2").value = "a"; + assert.equal(rows[0].cells[0].question.isVisible, false, "cell[0,0] is invisible"); + assert.equal(rows[1].cells[0].question.isVisible, true, "cell[1,0] is visible"); + questions = q.getNestedQuestions(true); + assert.equal(questions.length, 3, "3 cells"); + assert.equal(questions[0].name, "col2", "cells[0, 0], visible"); + assert.equal(questions[1].name, "col1", "cells[1, 1], visible"); + assert.equal(questions[2].name, "col2", "cells[1, 1], visible"); +}); QUnit.test("matrixDynamic.addConditionObjectsByContext + settings.matrixMaxRowCountInCondition=0", function (assert) { settings.matrixMaxRowCountInCondition = 0; var objs = []; diff --git a/tests/surveypaneldynamictests.ts b/tests/surveypaneldynamictests.ts index 684d0b0f37..96b3df7d98 100644 --- a/tests/surveypaneldynamictests.ts +++ b/tests/surveypaneldynamictests.ts @@ -1618,6 +1618,24 @@ QUnit.test("panelDynamic.addConditionObjectsByContext", function(assert) { "addConditionObjectsByContext work correctly for panel dynamic with context equals true" ); }); +QUnit.test("panelDynamic.getNestedQuestions", function(assert) { + const panel = new QuestionPanelDynamicModel("qPanel"); + panel.template.addNewQuestion("text", "q1"); + const q2 = new QuestionMultipleTextModel("q2"); + q2.title = "Question 2"; + q2.addItem("item1"); + q2.addItem("item2"); + panel.template.addQuestion(q2); + panel.panelCount = 2; + const questions = panel.getNestedQuestions(); + assert.equal(questions.length, 6, "two panels * 3"); + assert.equal(questions[0].name, "q1", "panel[0].q1"); + assert.equal(questions[1].name, "item1", "panel[0].q2.item1"); + assert.equal(questions[2].name, "item2", "panel[0].q2.item2"); + assert.equal(questions[3].name, "q1", "panel[1].q1"); + assert.equal(questions[4].name, "item1", "panel[1].q2.item1"); + assert.equal(questions[5].name, "item2", "panel[1].q2.item2"); +}); QUnit.test("panelDynamic.addConditionObjectsByContext + settings.panelDynamicMaxPanelCountInCondition = 0", function(assert) { settings.panelDynamicMaxPanelCountInCondition = 0; diff --git a/tests/surveyquestiontests.ts b/tests/surveyquestiontests.ts index 80587543d9..23cb90d12d 100644 --- a/tests/surveyquestiontests.ts +++ b/tests/surveyquestiontests.ts @@ -2328,6 +2328,15 @@ QUnit.test("question.addConditionObjectsByContext", function (assert) { "addConditionObjectsByContext work correctly" ); }); +QUnit.test("question.getNextedQuestions", function (assert) { + const q = new QuestionMultipleTextModel("q_mt"); + q.addItem("item1", "Item 1 title"); + q.addItem("item2"); + const nQuestions = q.getNestedQuestions(); + assert.equal(nQuestions.length, 2, "We have 2 items"); + assert.equal(nQuestions[0].name, "item1", "#1"); + assert.equal(nQuestions[1].name, "item2", "#2"); +}); QUnit.test("question.getConditionJson", function (assert) { var json = new QuestionHtmlModel("q_html").getConditionJson("equals"); diff --git a/tests/surveytests.ts b/tests/surveytests.ts index 74f941e328..e2b8ae26a3 100644 --- a/tests/surveytests.ts +++ b/tests/surveytests.ts @@ -17346,3 +17346,17 @@ QUnit.test("Check onPopupVisibleChanged events", function (assert) { q.value = "abc"; assert.equal(q.value, "ABC", "Convert to upper case"); }); +QUnit.test("survey.getNestedQuestions", function (assert) { + const survey = new SurveyModel({ + elements: [ + { type: "text", name: "q1" }, + { type: "multipletext", name: "q2", items: [{ name: "q2_item1" }, { name: "q2_item2" }] } + ] + }); + const questions = survey.getAllQuestions(false, false, true); + assert.equal(questions.length, 4, "3 questions"); + assert.equal(questions[0].name, "q1", "#1"); + assert.equal(questions[1].name, "q2", "#2"); + assert.equal(questions[2].name, "q2_item1", "#3"); + assert.equal(questions[3].name, "q2_item2", "#4"); +});