Skip to content

Commit

Permalink
Calculate renderedRateValues prop on demand & rateValues items Visibl…
Browse files Browse the repository at this point in the history
…eIf #9267 (#9337)

* Calcuate renderedRateValues prop on demand #9267

* Try to fix vue3 visual test

* Support rate value visibleIf #9267
  • Loading branch information
andrewtelnov authored Jan 20, 2025
1 parent 7b80b24 commit ceb0232
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 40 deletions.
10 changes: 8 additions & 2 deletions packages/survey-core/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
107 changes: 72 additions & 35 deletions packages/survey-core/src/question_rating.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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"],
() => {
Expand All @@ -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) => {
Expand Down Expand Up @@ -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<string>, func: any) {
this.registerFunctionOnPropertiesValueChanged(names,
Expand Down Expand Up @@ -219,7 +217,6 @@ export class QuestionRatingModel extends Question {
}
public set rateValues(val: Array<any>) {
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.
Expand Down Expand Up @@ -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 {
Expand All @@ -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<any>, properties: HashTable<any>): void {
super.runConditionCore(values, properties);
this.runRateItesmCondition(values, properties);
}
protected runRateItesmCondition(values: HashTable<any>, properties: HashTable<any>): 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<ItemValue> {
if(!this.useRateValues()) return this.createRateValues();
const items = new Array<ItemValue>();
this.rateValues.forEach(item => {
if(item.isVisible) {
items.push(item);
}
});
return items;
}
private calculateRateValues(): Array<ItemValue> {
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<RenderedRatingItem> {
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);
Expand All @@ -376,7 +396,29 @@ export class QuestionRatingModel extends Question {
return renderedItem;
});
}
@propertyArray() renderedRateItems: Array<RenderedRatingItem>;
private calculateVisibleChoices(): Array<ItemValue> {
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<RenderedRatingItem> {
return this.getPropertyValue("renderedRateItems", undefined, () => this.calculateRenderedRateItems());
}
public get visibleChoices(): ItemValue[] {
return this.getPropertyValue("visibleChoices", undefined, () => this.calculateVisibleChoices());
}

private createRateValues() {
var res = [];
Expand Down Expand Up @@ -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 : "";
Expand Down Expand Up @@ -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);
Expand Down
54 changes: 51 additions & 3 deletions packages/survey-core/tests/question_ratingtests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,10 +304,13 @@ QUnit.test("Check rateValues on text change", (assert) => {
const survey = new SurveyModel(json);
const q1 = <QuestionRatingModel>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 = {
Expand Down Expand Up @@ -1763,4 +1766,49 @@ QUnit.test("Check dropdown rating text, #8953", function (assert) {
});
const question = <QuestionRatingModel>survey.getAllQuestions()[0];
assert.deepEqual(question.visibleChoices.map(c => c.text), ["Label0", "Label1"]);
});
});
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 = <QuestionRatingModel>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");
});

0 comments on commit ceb0232

Please sign in to comment.