diff --git a/src/base-interfaces.ts b/src/base-interfaces.ts index cb133125af..dec9aa1d41 100644 --- a/src/base-interfaces.ts +++ b/src/base-interfaces.ts @@ -26,6 +26,7 @@ export interface ISurveyData { getAllValues(): any; getFilteredValues(): any; getFilteredProperties(): any; + findQuestionByName(name: string): IQuestion; } export interface ITextProcessor { processText(text: string, returnDisplayValue: boolean): string; diff --git a/src/question_baseselect.ts b/src/question_baseselect.ts index d1266888b1..7ef0c42752 100644 --- a/src/question_baseselect.ts +++ b/src/question_baseselect.ts @@ -33,6 +33,7 @@ export class QuestionSelectBase extends Question { private noneItemValue: ItemValue = new ItemValue(settings.noneItemValue); private newItemValue: ItemValue; private canShowOptionItemCallback: (item: ItemValue) => boolean; + private isUsingCarrayForward: boolean; @property() protected selectedItemValues: any; constructor(name: string) { @@ -236,6 +237,7 @@ export class QuestionSelectBase extends Question { } public runCondition(values: HashTable, properties: HashTable) { super.runCondition(values, properties); + if(this.isUsingCarrayForward) return; this.runItemsEnableCondition(values, properties); this.runItemsCondition(values, properties); } @@ -463,6 +465,7 @@ export class QuestionSelectBase extends Question { return; super.setQuestionValue(newValue, updateIsAnswered); this.setPropertyValue("renderedValue", this.rendredValueFromData(newValue)); + this.updateChoicesDependedQuestions(); if (this.hasComment || !updateComment) return; var isOtherSel = this.isOtherSelected; if (isOtherSel && !!this.prevOtherValue) { @@ -927,16 +930,17 @@ export class QuestionSelectBase extends Question { : this.activeChoices; } protected get activeChoices(): Array { - var question = this.getQuestionWithChoices(); - if (!!question) { + const question = this.getQuestionWithChoices(); + this.isUsingCarrayForward = !!question; + if (this.isUsingCarrayForward) { this.addIntoDependedQuestion(question); return this.getChoicesFromQuestion(question); } return this.choicesFromUrl ? this.choicesFromUrl : this.getChoices(); } private getQuestionWithChoices(): QuestionSelectBase { - if (!this.choicesFromQuestion || !this.survey) return null; - var res: any = this.survey.getQuestionByName(this.choicesFromQuestion); + if (!this.choicesFromQuestion || !this.data) return null; + var res: any = this.data.findQuestionByName(this.choicesFromQuestion); return !!res && !!res.visibleChoices && Array.isArray(res.dependedQuestions) && res !== this ? res : null; } protected getChoicesFromQuestion( @@ -1235,17 +1239,15 @@ export class QuestionSelectBase extends Question { } private isUpdatingChoicesDependedQuestions = false; protected updateChoicesDependedQuestions() { - if (this.isUpdatingChoicesDependedQuestions) return; + if (this.isLoadingFromJson || this.isUpdatingChoicesDependedQuestions) return; this.isUpdatingChoicesDependedQuestions = true; for (var i = 0; i < this.dependedQuestions.length; i++) { this.dependedQuestions[i].onVisibleChoicesChanged(); - this.dependedQuestions[i].updateChoicesDependedQuestions(); } this.isUpdatingChoicesDependedQuestions = false; } onSurveyValueChanged(newValue: any) { super.onSurveyValueChanged(newValue); - if (this.isLoadingFromJson) return; this.updateChoicesDependedQuestions(); } protected onVisibleChoicesChanged() { diff --git a/src/question_custom.ts b/src/question_custom.ts index 88bdf87679..c364cb659c 100644 --- a/src/question_custom.ts +++ b/src/question_custom.ts @@ -7,6 +7,7 @@ import { ITextProcessor, IPanel, IElement, + IQuestion, IProgressInfo } from "./base-interfaces"; import { SurveyElement } from "./survey-element"; @@ -488,6 +489,9 @@ export abstract class QuestionCustomModelBase extends Question getFilteredProperties(): any { return !!this.data ? this.data.getFilteredProperties() : {}; } + findQuestionByName(name: string): IQuestion { + return !!this.data ? this.data.findQuestionByName(name): null; + } //IPanel addElement(element: IElement, index: number) { } removeElement(element: IElement): boolean { @@ -722,6 +726,13 @@ export class QuestionCompositeModel extends QuestionCustomModelBase { getTextProcessor(): ITextProcessor { return this.textProcessing; } + findQuestionByName(name: string): IQuestion { + if(!!this.contentPanel) { + const res = this.contentPanel.getQuestionByName(name); + if(!!res) return res; + } + return super.findQuestionByName(name); + } protected clearValueIfInvisibleCore(): void { super.clearValueIfInvisibleCore(); var questions = this.contentPanel.questions; diff --git a/src/question_matrixdropdownbase.ts b/src/question_matrixdropdownbase.ts index 1624a24743..c3b50bbb4f 100644 --- a/src/question_matrixdropdownbase.ts +++ b/src/question_matrixdropdownbase.ts @@ -404,6 +404,15 @@ implements ISurveyData, ISurveyImpl, ILocalizableOwner { public setComment(name: string, newValue: string, locNotification: any) { this.setValueCore(name, newValue, true); } + findQuestionByName(name: string): IQuestion { + if(!name) return undefined; + const prefix = MatrixDropdownRowModelBase.RowVariableName + "."; + if(name.indexOf(prefix) === 0) { + return this.getQuestionByName(name.substring(prefix.length)); + } + const survey = this.getSurvey(); + return !!survey ? survey.getQuestionByName(name): null; + } private setValueCore(name: string, newColumnValue: any, isComment: boolean) { if (this.isSettingValue) return; this.updateQuestionsValue(name, newColumnValue, isComment); diff --git a/src/question_multipletext.ts b/src/question_multipletext.ts index 5949f22d8b..d8b5c61659 100644 --- a/src/question_multipletext.ts +++ b/src/question_multipletext.ts @@ -5,6 +5,7 @@ import { ISurvey, IPanel, IElement, + IQuestion, ITextProcessor, IProgressInfo } from "./base-interfaces"; @@ -256,6 +257,10 @@ export class MultipleTextItemModel extends Base getFilteredProperties(): any { return { survey: this.getSurvey() }; } + findQuestionByName(name: string): IQuestion { + const survey = this.getSurvey(); + return !!survey ? survey.getQuestionByName(name): null; + } //IValidatorOwner getValidatorTitle(): string { return this.title; diff --git a/src/question_paneldynamic.ts b/src/question_paneldynamic.ts index 03e0760541..2e78d99d08 100644 --- a/src/question_paneldynamic.ts +++ b/src/question_paneldynamic.ts @@ -157,6 +157,15 @@ export class QuestionPanelDynamicItem implements ISurveyData, ISurveyImpl { public setComment(name: string, newValue: string, locNotification: any) { this.setValue(name + settings.commentSuffix, newValue); } + findQuestionByName(name: string): IQuestion { + if(!name) return undefined; + const prefix = QuestionPanelDynamicItem.ItemVariableName + "."; + if(name.indexOf(prefix) === 0) { + return this.panel.getQuestionByName(name.substring(prefix.length)); + } + const survey = this.getSurvey(); + return !!survey ? survey.getQuestionByName(name): null; + } getAllValues(): any { return this.data.getPanelItemData(this); } diff --git a/src/survey.ts b/src/survey.ts index 03f6f6395e..c13c11e50b 100644 --- a/src/survey.ts +++ b/src/survey.ts @@ -5185,6 +5185,9 @@ export class SurveyModel extends SurveyElementCore if (!res) return null; return res[0]; } + findQuestionByName(name: string): IQuestion { + return this.getQuestionByName(name); + } /** * Returns a question by its value name * @param valueName a question name diff --git a/tests/question_customtests.ts b/tests/question_customtests.ts index f6d46e1adf..9004a6e12b 100644 --- a/tests/question_customtests.ts +++ b/tests/question_customtests.ts @@ -1894,4 +1894,27 @@ QUnit.test("Composite: getProgressInfo", function (assert) { requiredAnsweredQuestionCount: 0, }, "q4"); ComponentCollection.Instance.clear(); -}); \ No newline at end of file +}); +QUnit.test("Composite: Support carry-forward", function (assert) { + const json = { + name: "newquestion", + elementsJSON: [ + { type: "checkbox", name: "q1", choices: [1, 2, 3, 4, 5] }, + { type: "radiogroup", name: "q2", choicesFromQuestion: "q1", choicesFromQuestionMode: "selected" } + ] + }; + ComponentCollection.Instance.add(json); + const survey = new SurveyModel({ + elements: [{ type: "newquestion", name: "q1" }], + }); + const q = survey.getAllQuestions()[0]; + const q1 = q.contentPanel.getQuestionByName("q1"); + const q2 = q.contentPanel.getQuestionByName("q2"); + assert.equal(q2.choicesFromQuestion, "q1", "choicesFromQuestion is loaded"); + assert.equal(q2.choicesFromQuestionMode, "selected", "choicesFromQuestionMode is loaded"); + assert.equal(q2.visibleChoices.length, 0, "There is no visible choices"); + q1.value = [1, 3, 5]; + assert.equal(q2.visibleChoices.length, 3, "Choices are here"); + assert.equal(q2.visibleChoices[1].value, 3, "A choice value is correct"); + ComponentCollection.Instance.clear(); +}); diff --git a/tests/question_matrixdynamictests.ts b/tests/question_matrixdynamictests.ts index bcf40cc79d..6bc085e4dc 100644 --- a/tests/question_matrixdynamictests.ts +++ b/tests/question_matrixdynamictests.ts @@ -8001,3 +8001,30 @@ QUnit.test("Update expressions on setting matrixdropdown rows, Bug#5526", functi "matrix-total": { col1: 1, col3: 3 } }, "#2"); }); +QUnit.test("Carry forward in matrix cells", function (assert) { + const survey = new SurveyModel({ + "elements": [ + { + "type": "matrixdynamic", + "name": "matrix", + columns: [ + { cellType: "checkbox", name: "col1", choices: [1, 2, 3, 4, 5] }, + { cellType: "dropdown", name: "col2", choicesFromQuestion: "row.col1", choicesFromQuestionMode: "selected" } + ], + rowCount: 1 + } + ] + }); + const matrix = survey.getQuestionByName("matrix"); + const rows = matrix.visibleRows; + const cellQ1 = rows[0].cells[0].question; + const cellQ2 = rows[0].cells[1].question; + assert.equal("col1", cellQ1.name, "col1 question is correct"); + assert.equal("col2", cellQ2.name, "col2 question is correct"); + assert.equal(cellQ2.choicesFromQuestion, "row.col1", "choicesFromQuestion is loaded"); + assert.equal(cellQ2.choicesFromQuestionMode, "selected", "choicesFromQuestionMode is loaded"); + assert.equal(cellQ2.visibleChoices.length, 0, "There is no visible choices"); + cellQ1.value = [1, 3, 5]; + assert.equal(cellQ2.visibleChoices.length, 3, "Choices are here"); + assert.equal(cellQ2.visibleChoices[1].value, 3, "A choice value is correct"); +}); \ No newline at end of file diff --git a/tests/surveypaneldynamictests.ts b/tests/surveypaneldynamictests.ts index 10e9f13abd..0ad6b405a9 100644 --- a/tests/surveypaneldynamictests.ts +++ b/tests/surveypaneldynamictests.ts @@ -4915,3 +4915,27 @@ QUnit.test("NoentriesText and readOnly", (assert) => { survey.mode = "display"; assert.equal(panel1.noEntriesText.indexOf("There are no entries."), 0, "panel1: text for readonly"); }); +QUnit.test("Carry forward in panel dynamic", function (assert) { + const survey = new SurveyModel({ + "elements": [ + { + "type": "paneldynamic", + "name": "panel", + templateElements: [ + { type: "checkbox", name: "q1", choices: [1, 2, 3, 4, 5] }, + { type: "dropdown", name: "q2", choicesFromQuestion: "panel.q1", choicesFromQuestionMode: "selected" } + ], + panelCount: 1 + } + ] + }); + const panel = survey.getQuestionByName("panel").panels[0]; + const q1 = panel.getQuestionByName("q1"); + const q2 = panel.getQuestionByName("q2"); + assert.equal(q2.choicesFromQuestion, "panel.q1", "choicesFromQuestion is loaded"); + assert.equal(q2.choicesFromQuestionMode, "selected", "choicesFromQuestionMode is loaded"); + assert.equal(q2.visibleChoices.length, 0, "There is no visible choices"); + q1.value = [1, 3, 5]; + assert.equal(q2.visibleChoices.length, 3, "Choices are here"); + assert.equal(q2.visibleChoices[1].value, 3, "A choice value is correct"); +});