diff --git a/packages/survey-core/src/base.ts b/packages/survey-core/src/base.ts index 8fbb7215b9..e441bb644b 100644 --- a/packages/survey-core/src/base.ts +++ b/packages/survey-core/src/base.ts @@ -504,8 +504,14 @@ export class Base { if(!!calcFunc) { const newVal = calcFunc(); if(newVal !== undefined) { - this.setPropertyValueDirectly(name, newVal); - return newVal; + if(Array.isArray(newVal)) { + const array = this.createNewArray(name); + array.splice(0, 0, ...newVal); + return array; + } else { + this.setPropertyValueDirectly(name, newVal); + return newVal; + } } } const propDefaultValue = this.getDefaultPropertyValue(name); diff --git a/packages/survey-core/src/question_rating.ts b/packages/survey-core/src/question_rating.ts index 983b021f84..26e51f15f3 100644 --- a/packages/survey-core/src/question_rating.ts +++ b/packages/survey-core/src/question_rating.ts @@ -14,6 +14,7 @@ import { ISurveyImpl } from "./base-interfaces"; import { IsTouch } from "./utils/devices"; import { ITheme } from "./themes"; import { DomDocumentHelper } from "./global_variables_utils"; +import { HashTable } from "./helpers"; export class RenderedRatingItem extends Base { private onStringChangedCallback() { @@ -52,27 +53,26 @@ export class QuestionRatingModel extends Question { super(name); this.createItemValues("rateValues"); - this.createRenderedRateItems(); this.createLocalizableString("ratingOptionsCaption", this, false, true); this.registerFunctionOnPropertiesValueChanged(["rateMin", "rateMax", "minRateDescription", "maxRateDescription", "rateStep", "displayRateDescriptionsAsExtremeItems"], - () => this.createRenderedRateItems()); + () => this.resetRenderedItems()); this.registerFunctionOnPropertiesValueChanged(["rateType"], () => { this.setIconsToRateValues(); - this.createRenderedRateItems(); + this.resetRenderedItems(); this.updateRateCount(); }); this.registerFunctionOnPropertiesValueChanged(["rateValues"], () => { this.setIconsToRateValues(); - this.createRenderedRateItems(); + this.resetRenderedItems(); }); this.registerSychProperties(["rateValues"], () => { this.autoGenerate = this.rateValues.length == 0; this.setIconsToRateValues(); - this.createRenderedRateItems(); + this.resetRenderedItems(); }); this.registerFunctionOnPropertiesValueChanged(["rateColorMode", "scaleColorMode"], () => { @@ -90,7 +90,7 @@ export class QuestionRatingModel extends Question { this.rateValues.splice(0, this.rateValues.length); this.updateRateMax(); } - this.createRenderedRateItems(); + this.resetRenderedItems(); }); this.createLocalizableString("minRateDescription", this, true) .onStringChanged.add((sender, options) => { @@ -121,9 +121,7 @@ export class QuestionRatingModel extends Question { if (this.jsonObj.autoGenerate === undefined && this.jsonObj.rateValues !== undefined) this.autoGenerate = !this.jsonObj.rateValues.length; this.updateRateCount(); this.setIconsToRateValues(); - this.createRenderedRateItems(); } - private _syncPropertiesChanging: boolean = false; private registerSychProperties(names: Array, func: any) { this.registerFunctionOnPropertiesValueChanged(names, @@ -219,7 +217,6 @@ export class QuestionRatingModel extends Question { } public set rateValues(val: Array) { this.setPropertyValue("rateValues", val); - this.createRenderedRateItems(); } /** * Specifies the first rate value in the generated sequence of rate values. Applies if the [`rateValues`](https://surveyjs.io/form-library/documentation/api-reference/rating-scale-question-model#rateValues) array is empty. @@ -331,6 +328,7 @@ export class QuestionRatingModel extends Question { QuestionRatingModel.goodColorLight = getRGBColor("--sjs-special-green-light", "--sd-rating-good-color-light"); this.colorsCalculated = true; + this.resetRenderedItems(); } protected getDisplayValueCore(keysAsText: boolean, value: any): any { @@ -342,32 +340,54 @@ export class QuestionRatingModel extends Question { return this.renderedRateItems.map(i => i.itemValue); } protected supportEmptyValidation(): boolean { return this.renderAs === "dropdown"; } - public itemValuePropertyChanged( - item: ItemValue, - name: string, - oldValue: any, - newValue: any - ) { + public itemValuePropertyChanged(item: ItemValue, name: string, oldValue: any, newValue: any): void { if (!this.useRateValues() && newValue !== undefined) this.autoGenerate = false; super.itemValuePropertyChanged(item, name, oldValue, newValue); } - private createRenderedRateItems() { - let rateValues = []; - if (this.useRateValues()) { - rateValues = this.rateValues; - } - else { - rateValues = this.createRateValues(); + protected runConditionCore(values: HashTable, properties: HashTable): void { + super.runConditionCore(values, properties); + this.runRateItesmCondition(values, properties); + } + protected runRateItesmCondition(values: HashTable, properties: HashTable): void { + if(!this.useRateValues()) return; + let isChanged = false; + if(this.survey?.areInvisibleElementsShowing) { + this.rateValues.forEach(item => { + isChanged = isChanged || !item.isVisible; + item.setIsVisible(item, true); + }); + } else { + isChanged = ItemValue.runConditionsForItems(this.rateValues, undefined, undefined, values, properties, true); } - - if (this.autoGenerate) { - this.rateMax = rateValues[rateValues.length - 1].value; + if(isChanged) { + this.resetRenderedItems(); + if(!this.isEmpty() && !this.isReadOnly) { + const item = ItemValue.getItemByValue(this.rateValues, this.value); + if(item && !item.isVisible) { + this.clearValue(); + } + } } + } + private getRateValuesCore(): Array { + if(!this.useRateValues()) return this.createRateValues(); + const items = new Array(); + this.rateValues.forEach(item => { + if(item.isVisible) { + items.push(item); + } + }); + return items; + } + private calculateRateValues(): Array { + let rateValues = this.getRateValuesCore(); if (this.rateType == "smileys" && rateValues.length > 10) rateValues = rateValues.slice(0, 10); - - this.visibleChoicesValue = rateValues.map((i, idx) => this.getRatingItemValue(i, idx)); - this.renderedRateItems = rateValues.map((v, i) => { - let renderedItem = null; + return rateValues; + } + private calculateRenderedRateItems() : Array { + const rateValues = this.calculateRateValues(); + return rateValues.map((v, i) => { + let renderedItem: RenderedRatingItem = null; if (this.displayRateDescriptionsAsExtremeItems) { if (i == 0) renderedItem = new RenderedRatingItem(v, this.minRateDescription && this.locMinRateDescription || v.locText); if (i == rateValues.length - 1) renderedItem = new RenderedRatingItem(v, this.maxRateDescription && this.locMaxRateDescription || v.locText); @@ -376,7 +396,29 @@ export class QuestionRatingModel extends Question { return renderedItem; }); } - @propertyArray() renderedRateItems: Array; + private calculateVisibleChoices(): Array { + const rateValues = this.calculateRateValues(); + return rateValues.map((i, idx) => this.getRatingItemValue(i, idx)); + } + private iCounter = 0; + private resetRenderedItems() { + if (this.autoGenerate) { + const rateValues = this.getRateValuesCore(); + this.rateMax = rateValues[rateValues.length - 1].value; + } + if(Array.isArray(this.getPropertyValueWithoutDefault("renderedRateItems"))) { + this.setArrayPropertyDirectly("renderedRateItems", this.calculateRenderedRateItems()); + } + if(Array.isArray(this.getPropertyValueWithoutDefault("visibleChoices"))) { + this.setArrayPropertyDirectly("visibleChoices", this.calculateVisibleChoices); + } + } + public get renderedRateItems(): Array { + return this.getPropertyValue("renderedRateItems", undefined, () => this.calculateRenderedRateItems()); + } + public get visibleChoices(): ItemValue[] { + return this.getPropertyValue("visibleChoices", undefined, () => this.calculateVisibleChoices()); + } private createRateValues() { var res = []; @@ -844,10 +886,6 @@ export class QuestionRatingModel extends Question { public isItemSelected(item: ItemValue): boolean { return item.value == this.value; } - private visibleChoicesValue: ItemValue[]; - public get visibleChoices(): ItemValue[] { - return this.visibleChoicesValue; - } public get readOnlyText() { if (this.readOnly) return (this.displayValue || this.placeholder); return this.isEmpty() ? this.placeholder : ""; @@ -910,7 +948,6 @@ export class QuestionRatingModel extends Question { public themeChanged(theme: ITheme): void { this.colorsCalculated = false; this.updateColors(theme.cssVariables); - this.createRenderedRateItems(); } public setSurveyImpl(value: ISurveyImpl, isLight?: boolean) { super.setSurveyImpl(value, isLight); diff --git a/packages/survey-core/tests/question_ratingtests.ts b/packages/survey-core/tests/question_ratingtests.ts index 3fc7a9f4fb..4685d73fb3 100644 --- a/packages/survey-core/tests/question_ratingtests.ts +++ b/packages/survey-core/tests/question_ratingtests.ts @@ -306,10 +306,13 @@ QUnit.test("Check rateValues on text change", (assert) => { const survey = new SurveyModel(json); const q1 = survey.getQuestionByName("q1"); assert.equal(q1.rateValues.length, 0); - var oldRendered = q1.renderedRateValues; + let oldRendered = q1.renderedRateItems; q1.visibleRateValues[0].text = "abc"; assert.equal(q1.rateValues.length, 5); - assert.equal(q1.renderedRateValues, oldRendered, "renderedRateValues is not cloned"); + assert.strictEqual(q1.renderedRateItems, oldRendered, "renderedRateItems is cloned"); + oldRendered = q1.renderedRateItems; + q1.visibleRateValues[1].text = "abc"; + assert.strictEqual(q1.renderedRateItems, oldRendered, "renderedRateItems is not cloned"); }); QUnit.test("Check cssClasses update when dropdownListModel is set", (assert) => { var json = { @@ -1766,4 +1769,49 @@ QUnit.test("Check dropdown rating text, #8953", function (assert) { }); const question = survey.getAllQuestions()[0]; assert.deepEqual(question.visibleChoices.map(c => c.text), ["Label0", "Label1"]); -}); \ No newline at end of file +}); +QUnit.test("Ranking: items visibleIf and value, Bug#5959", function(assert) { + var survey = new SurveyModel({ + elements: [ + { type: "checkbox", name: "q1", choices: [1, 2] }, + { + type: "rating", + name: "q2", + rateValues: [ + { value: "a", visibleIf: "{q1} contains 1" }, + { value: "b", visibleIf: "{q1} contains 1" }, + { value: "c", visibleIf: "{q1} contains 2" }, + { value: "d", visibleIf: "{q1} contains 2" }, + { value: "e", visibleIf: "{q1} contains 1" }, + ] + } + ] + }); + const q1 = survey.getQuestionByName("q1"); + const q2 = survey.getQuestionByName("q2"); + assert.equal(q2.visibleRateValues.length, 0, "visibleRateValues #1"); + assert.equal(q2.renderedRateItems.length, 0, "renderedRateItems #1"); + q1.value = [1]; + assert.equal(q2.visibleRateValues.length, 3, "visibleRateValues #2"); + assert.equal(q2.renderedRateItems.length, 3, "renderedRateItems #2"); + q1.value = [2]; + assert.equal(q2.visibleRateValues.length, 2, "visibleRateValues #3"); + assert.equal(q2.renderedRateItems.length, 2, "renderedRateItems #3"); + q1.value = [1, 2]; + assert.equal(q2.visibleRateValues.length, 5, "visibleRateValues #4"); + assert.equal(q2.renderedRateItems.length, 5, "renderedRateItems #4"); + q1.value = []; + assert.equal(q2.visibleRateValues.length, 0, "visibleRateValues #5"); + assert.equal(q2.renderedRateItems.length, 0, "renderedRateItems #5 "); + survey.showInvisibleElements = true; + assert.equal(q2.renderedRateItems.length, 5, "renderedRateItems #6"); + survey.showInvisibleElements = false; + assert.equal(q2.renderedRateItems.length, 0, "renderedRateItems #7"); + q1.value = [1]; + assert.equal(q2.renderedRateItems.length, 3, "renderedRateItems #8"); + q2.value = "b"; + assert.deepEqual(q2.value, "b", "value set correctly, #8"); + q1.value = [2]; + assert.equal(q2.renderedRateItems.length, 2, "renderedRateItems #9"); + assert.deepEqual(q2.isEmpty(), true, "value is reset, #9"); +});