Skip to content

Commit

Permalink
Support matrix/panel dynamic for choicesFromQuestion #6061 (#6684)
Browse files Browse the repository at this point in the history
* Support matrix/panel dynamic for choicesFromQuestion #6061

* Implement choicesFromValueName&choicesFromTextName #6061

* Refactor code to avoid code duplication for array value questions #6061
  • Loading branch information
andrewtelnov authored Aug 11, 2023
1 parent b7ff8f2 commit 23a1ebb
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 42 deletions.
25 changes: 24 additions & 1 deletion src/question.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export class Question extends SurveyElement<Question>
private locProcessedTitle: LocalizableString;
protected isReadyValue: boolean = true;
private commentElements: Array<HTMLElement>;
private dependedQuestions: Array<Question> = [];

/**
* An event that is raised when the question's ready state has changed (expressions are evaluated, choices are loaded from a web resource specified by the `choicesByUrl` property, etc.).
Expand Down Expand Up @@ -268,6 +269,24 @@ export class Question extends SurveyElement<Question>
this.removeSelfFromList(this.parent.elements);
}
}
protected addDependedQuestion(question: Question): void {
if (!question || this.dependedQuestions.indexOf(question) > -1) return;
this.dependedQuestions.push(question);
}
protected removeDependedQuestion(question: Question): void {
if (!question) return;
var index = this.dependedQuestions.indexOf(question);
if (index > -1) {
this.dependedQuestions.splice(index, 1);
}
}
protected updateDependedQuestions(): void {
for (var i = 0; i < this.dependedQuestions.length; i++) {
this.dependedQuestions[i].updateDependedQuestion();
}
}
protected updateDependedQuestion(): void {}
protected resetDependedQuestion(): void {}
public get isFlowLayout(): boolean {
return this.getLayoutType() === "flow";
}
Expand Down Expand Up @@ -1264,6 +1283,7 @@ export class Question extends SurveyElement<Question>
this.setPropertyValue("comment", val);
this.fireCallback(this.commentChangedCallback);
}
public get isValueArray(): boolean { return false; }
/**
* Gets or sets the question value.
* @see SurveyModel.setValue
Expand Down Expand Up @@ -2235,8 +2255,11 @@ export class Question extends SurveyElement<Question>
this.renderAs = this.getDesktopRenderAs();
}
}
public dispose() {
public dispose(): void {
super.dispose();
for (var i = 0; i < this.dependedQuestions.length; i++) {
this.dependedQuestions[i].resetDependedQuestion();
}
this.destroyResizeObserver();
}
}
Expand Down
140 changes: 99 additions & 41 deletions src/question_baseselect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export class QuestionSelectBase extends Question {
private cachedValueForUrlRequests: any;
private isChoicesLoaded: boolean;
private enableOnLoadingChoices: boolean;
private dependedQuestions: Array<QuestionSelectBase> = [];
private noneItemValue: ItemValue = new ItemValue(settings.noneItemValue);
private newItemValue: ItemValue;
private canShowOptionItemCallback: (item: ItemValue) => boolean;
Expand Down Expand Up @@ -57,7 +56,7 @@ export class QuestionSelectBase extends Question {
}
});
this.registerPropertyChangedHandlers(
["choicesFromQuestion", "choicesFromQuestionMode", "showNoneItem"],
["choicesFromQuestion", "choicesFromQuestionMode", "choicesFromValueName", "choicesFromTextName", "showNoneItem"],
() => {
this.onVisibleChoicesChanged();
}
Expand Down Expand Up @@ -93,12 +92,15 @@ export class QuestionSelectBase extends Question {
public getType(): string {
return "selectbase";
}
public dispose() {
public dispose(): void {
super.dispose();
for (var i = 0; i < this.dependedQuestions.length; i++) {
this.dependedQuestions[i].choicesFromQuestion = "";
const q = this.getQuestionWithChoices();
if(!!q) {
q.removeDependedQuestion(this);
}
this.removeFromDependedQuestion(this.getQuestionWithChoices());
}
protected resetDependedQuestion(): void {
this.choicesFromQuestion = "";
}
public get otherId(): string {
return this.id + "_other";
Expand All @@ -116,24 +118,28 @@ export class QuestionSelectBase extends Question {
return res;
}
public get isUsingCarryForward(): boolean {
return this.getPropertyValue("isUsingCarrayForward", false);
return !!this.carryForwardQuestionType;
}
public get carryForwardQuestionType(): string {
return this.getPropertyValue("carryForwardQuestionType");
}
private setIsUsingCarrayForward(val: boolean): void {
this.setPropertyValue("isUsingCarrayForward", val);
private setCarryForwardQuestionType(selBaseQuestion: boolean, arrayQuestion: boolean): void {
const mode = selBaseQuestion ? "select" : (arrayQuestion ? "array" : undefined);
this.setPropertyValue("carryForwardQuestionType", mode);
}
public supportGoNextPageError() {
public supportGoNextPageError(): boolean {
return !this.isOtherSelected || !!this.otherValue;
}
isLayoutTypeSupported(layoutType: string): boolean {
return true;
}
public localeChanged() {
public localeChanged(): void {
super.localeChanged();
if (this.choicesOrder !== "none") {
this.updateVisibleChoices();
}
}
public locStrsChanged() {
public locStrsChanged(): void {
super.locStrsChanged();
if (!!this.choicesFromUrl) {
ItemValue.locStrsChanged(this.choicesFromUrl);
Expand Down Expand Up @@ -689,23 +695,12 @@ export class QuestionSelectBase extends Question {
var question = this.getQuestionWithChoices();
this.isLockVisibleChoices = !!question && question.name === val;
if (!!question && question.name !== val) {
question.removeFromDependedQuestion(this);
question.removeDependedQuestion(this);
}
this.setPropertyValue("choicesFromQuestion", val);
this.isLockVisibleChoices = false;
}
private isLockVisibleChoices: boolean;
private addIntoDependedQuestion(question: QuestionSelectBase) {
if (!question || question.dependedQuestions.indexOf(this) > -1) return;
question.dependedQuestions.push(this);
}
private removeFromDependedQuestion(question: QuestionSelectBase) {
if (!question) return;
var index = question.dependedQuestions.indexOf(this);
if (index > -1) {
question.dependedQuestions.splice(index, 1);
}
}
/**
* Specifies which choice items to inherit from another question. Applies only when the `choicesFromQuestion` property is specified.
*
Expand All @@ -725,6 +720,18 @@ export class QuestionSelectBase extends Question {
public set choicesFromQuestionMode(val: string) {
this.setPropertyValue("choicesFromQuestionMode", val);
}
public get choicesFromValueName(): string {
return this.getPropertyValue("choicesFromValueName");
}
public set choicesFromValueName(val: string) {
this.setPropertyValue("choicesFromValueName", val);
}
public get choicesFromTextName(): string {
return this.getPropertyValue("choicesFromTextName");
}
public set choicesFromTextName(val: string) {
this.setPropertyValue("choicesFromTextName", val);
}
/**
* Specifies whether to hide the question if no choice items are visible.
*
Expand Down Expand Up @@ -1012,29 +1019,66 @@ export class QuestionSelectBase extends Question {
: this.activeChoices;
}
protected get activeChoices(): Array<ItemValue> {
const question = this.getQuestionWithChoices();
this.setIsUsingCarrayForward(!!question);
if (this.isUsingCarryForward) {
this.addIntoDependedQuestion(question);
return this.getChoicesFromQuestion(question);
const question = this.findCarryForwardQuestion();
const selBaseQuestion = this.getQuestionWithChoicesCore(question);
const arrayQuestion = !selBaseQuestion ? this.getQuestionWithArrayValue(question) : null;
this.setCarryForwardQuestionType(!!selBaseQuestion, !!arrayQuestion);
if (this.carryForwardQuestionType === "select") {
selBaseQuestion.addDependedQuestion(this);
return this.getChoicesFromSelectQuestion(selBaseQuestion);
}
if (this.carryForwardQuestionType === "array") {
(<any>arrayQuestion).addDependedQuestion(this);
return this.getChoicesFromArrayQuestion(arrayQuestion);
}
return this.choicesFromUrl ? this.choicesFromUrl : this.getChoices();
}
private getQuestionWithChoices(): QuestionSelectBase {
return this.getQuestionWithChoicesCore(this.findCarryForwardQuestion());
}
private findCarryForwardQuestion(): Question {
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;
return <Question>this.data.findQuestionByName(this.choicesFromQuestion);
}
private getQuestionWithChoicesCore(question: Question): QuestionSelectBase {
if(!!question && !!question.visibleChoices && (Serializer.isDescendantOf(question.getType(), "selectbase")) && question !== this)
return <QuestionSelectBase>question;
return null;
}
private getQuestionWithArrayValue(question: Question): Question {
return !!question && question.isValueArray ? question : null;
}
private getChoicesFromArrayQuestion(question: Question): Array<ItemValue> {
if (this.isDesignMode) return [];
const val = question.value;
if(!Array.isArray(val)) return [];
const res: Array<ItemValue> = [];
for(var i = 0; i < val.length; i ++) {
const obj = val[i];
if(!Helpers.isValueObject(obj)) continue;
const key = this.getValueKeyName(obj);
if(!!key && !this.isValueEmpty(obj[key])) {
const text = !!this.choicesFromTextName ? obj[this.choicesFromTextName] : undefined;
res.push(this.createItemValue(obj[key], text));
}
}
return res;
}
private getValueKeyName(obj: any): string {
if(this.choicesFromValueName) return this.choicesFromValueName;
const keys = Object.keys(obj);
return keys.length > 0 ? keys[0] : undefined;
}
private getChoicesFromQuestion(question: QuestionSelectBase): Array<ItemValue> {
private getChoicesFromSelectQuestion(question: QuestionSelectBase): Array<ItemValue> {
if (this.isDesignMode) return [];
var res: Array<ItemValue> = [];
const res: Array<ItemValue> = [];
var isSelected =
this.choicesFromQuestionMode == "selected"
? true
: this.choicesFromQuestionMode == "unselected"
? false
: undefined;
var choices = question.visibleChoices;
const choices = question.visibleChoices;
for (var i = 0; i < choices.length; i++) {
if (this.isBuiltInChoice(choices[i], question)) continue;
if (isSelected === undefined) {
Expand Down Expand Up @@ -1339,13 +1383,13 @@ export class QuestionSelectBase extends Question {
if (this.isLoadingFromJson || this.isUpdatingChoicesDependedQuestions ||
!this.allowNotifyValueChanged || this.choicesByUrl.isRunning) return;
this.isUpdatingChoicesDependedQuestions = true;
for (var i = 0; i < this.dependedQuestions.length; i++) {
const q = this.dependedQuestions[i];
q.onVisibleChoicesChanged();
q.clearIncorrectValues();
}
this.updateDependedQuestions();
this.isUpdatingChoicesDependedQuestions = false;
}
protected updateDependedQuestion(): void {
this.onVisibleChoicesChanged();
this.clearIncorrectValues();
}
onSurveyValueChanged(newValue: any) {
super.onSurveyValueChanged(newValue);
this.updateChoicesDependedQuestions();
Expand Down Expand Up @@ -1738,7 +1782,7 @@ Serializer.addClass(
"selectbase",
[
{ name: "showCommentArea:switch", layout: "row", visible: true, category: "general" },
"choicesFromQuestion:question_selectbase",
"choicesFromQuestion:question_carryforward",
{
name: "choices:itemvalue[]", uniqueProperty: "value",
baseValue: function () {
Expand All @@ -1755,7 +1799,21 @@ Serializer.addClass(
choices: ["all", "selected", "unselected"],
dependsOn: "choicesFromQuestion",
visibleIf: (obj: any) => {
return !!obj.choicesFromQuestion;
return obj.carryForwardQuestionType === "select";
},
},
{
name: "choicesFromValueName",
dependsOn: "choicesFromQuestion",
visibleIf: (obj: any) => {
return obj.carryForwardQuestionType === "array";
},
},
{
name: "choicesFromTextName",
dependsOn: "choicesFromQuestion",
visibleIf: (obj: any) => {
return obj.carryForwardQuestionType === "array";
},
},
{
Expand Down
1 change: 1 addition & 0 deletions src/question_checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export class QuestionCheckboxModel extends QuestionCheckboxBase {
if (!val) return val;
return !this.valuePropertyName ? val : val[this.valuePropertyName];
}
public get isValueArray(): boolean { return true; }
/**
* Specifies the maximum number of selected choices.
*
Expand Down
1 change: 1 addition & 0 deletions src/question_matrixdynamic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export class QuestionMatrixDynamicModel extends QuestionMatrixDropdownModelBase
public set confirmDelete(val: boolean) {
this.setPropertyValue("confirmDelete", val);
}
public get isValueArray(): boolean { return true; }
/**
* Specifies a key column. Set this property to a column name, and the question will display `keyDuplicationError` if a user tries to enter a duplicate value in this column.
* @see keyDuplicationError
Expand Down
1 change: 1 addition & 0 deletions src/question_paneldynamic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,7 @@ export class QuestionPanelDynamicModel extends Question
}
this.value = newValue;
}
public get isValueArray(): boolean { return true; }
public isEmpty(): boolean {
var val = this.value;
if (!val || !Array.isArray(val)) return true;
Expand Down
Loading

0 comments on commit 23a1ebb

Please sign in to comment.