From 8a67f1e6bbca891eb03f4b64c3ecd28591c05895 Mon Sep 17 00:00:00 2001 From: Andrew Telnov Date: Mon, 15 Jan 2024 13:01:55 +0200 Subject: [PATCH] Do not update choices on loading data from web if the data is the same fix #7674 --- src/base.ts | 10 ++++++++++ src/helpers.ts | 12 +++--------- src/question_baseselect.ts | 14 ++++++++++++-- src/survey-error.ts | 2 +- src/survey.ts | 4 ++++ tests/choicesRestfultests.ts | 36 +++++++++++++++++++++++++++++++++--- tests/helperstests.ts | 18 ++++++++++++++++++ 7 files changed, 81 insertions(+), 15 deletions(-) diff --git a/src/base.ts b/src/base.ts index bdd597e37b..b3ce6027c4 100644 --- a/src/base.ts +++ b/src/base.ts @@ -218,6 +218,16 @@ export class Base { } return Helpers.isValueEmpty(value); } + public equals(obj: Base): boolean { + if(!obj) return false; + if (this.isDisposed || obj.isDisposed) return false; + if(this.getType() != obj.getType()) return false; + return this.equalsCore(obj); + } + protected equalsCore(obj: Base): boolean { + if((this).name !== (obj).name) return false; + return Helpers.isTwoValueEquals(this.toJSON(), obj.toJSON(), false, true, false); + } protected trimValue(value: any): any { if (!!value && (typeof value === "string" || value instanceof String)) return value.trim(); diff --git a/src/helpers.ts b/src/helpers.ts index cf345be3a7..dc6f86aa39 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -135,16 +135,10 @@ export class Helpers { } if (!Helpers.isValueObject(x) && !Helpers.isValueObject(y)) return x == y; if (!Helpers.isValueObject(x) || !Helpers.isValueObject(y)) return false; - if (x["equals"]) return x.equals(y); - if (!!x.toJSON && !!y.toJSON && !!x.getType && !!y.getType) { - if (x.isDisposed || y.isDisposed) return false; - if (x.getType() !== y.getType()) return false; - if (!!x.name && x.name !== y.name) return false; - return this.isTwoValueEquals(x.toJSON(), y.toJSON(), ignoreOrder, caseSensitive, trimStrings); - } - if (Array.isArray(x) && Array.isArray(y)) + if (x["equals"] && y["equals"]) return x.equals(y); + if (Array.isArray(x) && Array.isArray(y)) { return Helpers.isArraysEqual(x, y, ignoreOrder, caseSensitive, trimStrings); - if(!!x.equalsTo && y.equalsTo) return x.equalsTo(y); + } for (var p in x) { if (!x.hasOwnProperty(p)) continue; diff --git a/src/question_baseselect.ts b/src/question_baseselect.ts index fcee52ad02..f36d2cac6c 100644 --- a/src/question_baseselect.ts +++ b/src/question_baseselect.ts @@ -1318,7 +1318,7 @@ export class QuestionSelectBase extends Question { this.readOnly = true; } } - protected onLoadChoicesFromUrl(array: Array) { + protected onLoadChoicesFromUrl(array: Array): void { if (this.enableOnLoadingChoices) { this.readOnly = false; } @@ -1341,7 +1341,6 @@ export class QuestionSelectBase extends Question { if (this.isValueEmpty(this.cachedValueForUrlRequests)) { this.cachedValueForUrlRequests = this.value; } - this.isFirstLoadChoicesFromUrl = false; var cachedValues = this.createCachedValueForUrlRequests( this.cachedValueForUrlRequests, checkCachedValuesOnExisting @@ -1355,6 +1354,17 @@ export class QuestionSelectBase extends Question { newChoices[i].locOwner = this; } } + this.setChoicesFromUrl(newChoices, errors, cachedValues); + } + private canAvoidSettChoicesFromUrl(newChoices: Array): boolean { + if(this.isFirstLoadChoicesFromUrl) return false; + const chocesAreEmpty = !newChoices || Array.isArray(newChoices) && newChoices.length === 0; + if(chocesAreEmpty && !this.isEmpty()) return false; + return Helpers.isTwoValueEquals(this.choicesFromUrl, newChoices); + } + private setChoicesFromUrl(newChoices: Array, errors: Array, cachedValues: any): void { + if(this.canAvoidSettChoicesFromUrl(newChoices)) return; + this.isFirstLoadChoicesFromUrl = false; this.choicesFromUrl = newChoices; this.filterItems(); this.onVisibleChoicesChanged(); diff --git a/src/survey-error.ts b/src/survey-error.ts index c0b661422d..61aaf0d4f0 100644 --- a/src/survey-error.ts +++ b/src/survey-error.ts @@ -9,7 +9,7 @@ export class SurveyError { public text: string = null, protected errorOwner: ISurveyErrorOwner = null ) {} - public equalsTo(error: SurveyError): boolean { + public equals(error: SurveyError): boolean { if(!error || !error.getErrorType) return false; if(this.getErrorType() !== error.getErrorType()) return false; return this.text === error.text && this.visible === error.visible; diff --git a/src/survey.ts b/src/survey.ts index f1f5c5d545..26919d3d7a 100644 --- a/src/survey.ts +++ b/src/survey.ts @@ -1872,6 +1872,7 @@ export class SurveyModel extends SurveyElementCore return this.locale; } public locStrsChanged(): void { + if(this.isClearingUnsedValues) return; super.locStrsChanged(); if (!this.currentPage) return; if (this.isDesignMode) { @@ -6218,12 +6219,15 @@ export class SurveyModel extends SurveyElementCore var pos = Math.max(pos1, pos2); return name.substring(0, pos); } + private isClearingUnsedValues: boolean; private clearUnusedValues() { + this.isClearingUnsedValues = true; var questions = this.getAllQuestions(); for (var i: number = 0; i < questions.length; i++) { questions[i].clearUnusedValues(); } this.clearInvisibleQuestionValues(); + this.isClearingUnsedValues = false; } hasVisibleQuestionByValueName(valueName: string): boolean { var questions = this.getQuestionsByValueName(valueName); diff --git a/tests/choicesRestfultests.ts b/tests/choicesRestfultests.ts index 86409540fa..1038fbe259 100644 --- a/tests/choicesRestfultests.ts +++ b/tests/choicesRestfultests.ts @@ -1,5 +1,5 @@ import { Base, ArrayChanges } from "../src/base"; -import { ITextProcessor } from "../src/base-interfaces"; +import { IQuestion, ITextProcessor } from "../src/base-interfaces"; import { SurveyModel } from "../src/survey"; import { Question } from "../src/question"; import { ChoicesRestful } from "../src/choicesRestful"; @@ -770,10 +770,40 @@ QUnit.test("Clear value on getting empty array, bug #6251", function(assert) { question.choicesByUrl.error = new SurveyError("Empty request"); question["onLoadChoicesFromUrl"]([]); assert.equal(question.isEmpty(), true, "value is empty"); - assert.equal(question.selectedItem, null, "selectedItem is null"); + const isSelected = !!question.selectedItem; + assert.equal(isSelected, false, "selectedItem is null"); assert.equal(question.errors.length, 1, "It shows error on empty result"); }); - +QUnit.test("Do not call loadedChoicesFromServer on setting same items", function(assert) { + var survey = new SurveyModel(); + let counter = 0; + survey.loadedChoicesFromServer = (question: IQuestion) => { + counter ++; + }; + survey.addNewPage("1"); + var question = new QuestionDropdownModelTester("q1"); + question.choicesByUrl.url = "{state}"; + survey.pages[0].addQuestion(question); + question.hasItemsCallbackDelay = true; + question.onSurveyLoad(); + question["onLoadChoicesFromUrl"]([]); + assert.equal(counter, 1, "#1"); + question["onLoadChoicesFromUrl"]([]); + question["onLoadChoicesFromUrl"]([]); + assert.equal(counter, 1, "#2"); + question["onLoadChoicesFromUrl"]([new ItemValue("CA"), new ItemValue("TX")]); + assert.equal(counter, 2, "#3"); + question["onLoadChoicesFromUrl"]([new ItemValue("CA"), new ItemValue("TX")]); + question["onLoadChoicesFromUrl"]([new ItemValue("CA"), new ItemValue("TX")]); + assert.equal(counter, 2, "#4"); + question["onLoadChoicesFromUrl"]([]); + assert.equal(counter, 3, "#5"); + question["onLoadChoicesFromUrl"]([]); + assert.equal(counter, 3, "#6"); + survey.setValue("q1", "CA"); + question["onLoadChoicesFromUrl"]([]); + assert.equal(counter, 4, "#7"); +}); QUnit.test( "Set value before loading data + storeOthersAsComment, bug #1089", function(assert) { diff --git a/tests/helperstests.ts b/tests/helperstests.ts index a91a311adb..8c8d458d80 100644 --- a/tests/helperstests.ts +++ b/tests/helperstests.ts @@ -6,6 +6,8 @@ import { Base } from "../src/base"; import { property } from "../src/jsonobject"; import { settings } from "../src/settings"; import { SurveyError } from "../src/survey-error"; +import { QuestionTextModel } from "../src/question_text"; +import { QuestionCommentModel } from "../src/question_comment"; export default QUnit.module("Helpers"); @@ -509,4 +511,20 @@ QUnit.test("isValueObject", function(assert) { assert.equal(Helpers.isValueObject([1], true), false, "[1], exclude array"); assert.equal(Helpers.isValueObject({ a: "abc" }), true, "{ a: 'abc' }"); assert.equal(Helpers.isValueObject({ a: "abc" }, true), true, "{ a: 'abc' }, exclude array"); +}); +QUnit.test("base.equals", function(assert) { + const q1 = new QuestionTextModel("q1"); + q1.title = "title1"; + const q2 = new QuestionTextModel("q1"); + q2.title = "title2"; + const q3 = new QuestionTextModel("q1"); + q3.title = "title1"; + const q4 = new QuestionTextModel("q2"); + q4.title = "title1"; + const q5 = new QuestionCommentModel("q1"); + q5.title = "title1"; + assert.equal(Helpers.isTwoValueEquals(q1, q2), false, "#1"); + assert.equal(Helpers.isTwoValueEquals(q1, q3), true, "#2"); + assert.equal(Helpers.isTwoValueEquals(q1, q4), false, "#3"); + assert.equal(Helpers.isTwoValueEquals(q1, q5), false, "#4"); }); \ No newline at end of file