diff --git a/src/question_checkbox.ts b/src/question_checkbox.ts index a133670469..80951c03de 100644 --- a/src/question_checkbox.ts +++ b/src/question_checkbox.ts @@ -10,6 +10,8 @@ import { surveyLocalization } from "./surveyStrings"; import { LocalizableString } from "./localizablestring"; import { CssClassBuilder } from "./utils/cssClassBuilder"; import { IQuestion } from "./base-interfaces"; +import { SurveyError } from "./survey-error"; +import { CustomError } from "./error"; /** * A class that describes the Checkbox question type. @@ -182,6 +184,20 @@ export class QuestionCheckboxModel extends QuestionCheckboxBase { this.setPropertyValue("maxSelectedChoices", val); this.filterItems(); } + /** + * Sets a limit on the number of selected choices. + * + * Default value: 0 (unlimited) + * + * > This property only limits the number of choice items that can be selected by users. You can select any number of choice items in code, regardless of the `maxSelectedChoices` value. + */ + public get minSelectedChoices(): number { + return this.getPropertyValue("minSelectedChoices"); + } + public set minSelectedChoices(val: number) { + if (val < 0) val = 0; + this.setPropertyValue("minSelectedChoices", val); + } /** * An array of selected choice items. Includes the "Other" and "None" choice items if they are selected, but not "Select All". Items are sorted in the order they were selected. * @see visibleChoices @@ -215,6 +231,23 @@ export class QuestionCheckboxModel extends QuestionCheckboxBase { const val = this.renderedValue as Array; return val.map((item: any) => new ItemValue(item)); } + + protected onCheckForErrors( + errors: Array, + isOnValueChanged: boolean + ) { + super.onCheckForErrors(errors, isOnValueChanged); + if (isOnValueChanged) return; + + if (this.minSelectedChoices > 0 && this.checkMinSelectedChoicesUnreached()) { + const minError = new CustomError( + this.getLocalizationFormatString("minSelectError", this.minSelectedChoices), + this + ); + errors.push(minError); + } + } + protected onEnableItemCallBack(item: ItemValue): boolean { if (!this.shouldCheckMaxSelectedChoices()) return true; return this.isItemSelected(item); @@ -242,6 +275,14 @@ export class QuestionCheckboxModel extends QuestionCheckboxBase { var len = !Array.isArray(val) ? 0 : val.length; return len >= this.maxSelectedChoices; } + + private checkMinSelectedChoicesUnreached(): boolean { + if (this.minSelectedChoices < 1) return false; + var val = this.value; + var len = !Array.isArray(val) ? 0 : val.length; + return len < this.minSelectedChoices; + } + protected getItemClassCore(item: any, options: any) { const __dummy_value = this.value; //trigger dependencies from koValue for knockout options.isSelectAllItem = item === this.selectAllItem; @@ -439,6 +480,7 @@ export class QuestionCheckboxModel extends QuestionCheckboxBase { json["type"] = "radiogroup"; } json["maxSelectedChoices"] = 0; + json["minSelectedChoices"] = 0; return json; } public isAnswerCorrect(): boolean { @@ -537,6 +579,7 @@ Serializer.addClass( { name: "showSelectAllItem:boolean", alternativeName: "hasSelectAll" }, { name: "separateSpecialChoices", visible: true }, { name: "maxSelectedChoices:number", default: 0 }, + { name: "minSelectedChoices:number", default: 0 }, { name: "selectAllText", serializationProperty: "locSelectAllText", diff --git a/src/question_ranking.ts b/src/question_ranking.ts index 295abc6be7..ad6dd23729 100644 --- a/src/question_ranking.ts +++ b/src/question_ranking.ts @@ -547,6 +547,7 @@ Serializer.addClass( { name: "selectAllText", visible: false, isSerializable: false }, { name: "colCount:number", visible: false, isSerializable: false }, { name: "maxSelectedChoices", visible: false, isSerializable: false }, + { name: "minSelectedChoices", visible: false, isSerializable: false }, { name: "separateSpecialChoices", visible: false, isSerializable: false }, { name: "longTap", diff --git a/src/question_text.ts b/src/question_text.ts index 2991c75997..9b7ac810b2 100644 --- a/src/question_text.ts +++ b/src/question_text.ts @@ -2,7 +2,7 @@ import { QuestionFactory } from "./questionfactory"; import { Serializer } from "./jsonobject"; import { LocalizableString, LocalizableStrings } from "./localizablestring"; import { Helpers, HashTable } from "./helpers"; -import { EmailValidator, SurveyValidator } from "./validator"; +import { EmailValidator } from "./validator"; import { SurveyError } from "./survey-error"; import { CustomError } from "./error"; import { settings } from "./settings"; @@ -71,16 +71,7 @@ export class QuestionTextModel extends QuestionTextBase { this.setRenderedMinMax(values, properties); } } - public getValidators(): Array { - var validators = super.getValidators(); - if ( - this.inputType === "email" && - !this.validators.some((v) => v.getType() === "emailvalidator") - ) { - validators.push(new EmailValidator()); - } - return validators; - } + isLayoutTypeSupported(layoutType: string): boolean { return true; } @@ -257,7 +248,22 @@ export class QuestionTextModel extends QuestionTextBase { ); }; errors.push(maxError); } + + var name = this.name; + var emailValidator = new EmailValidator(); + if ( + this.inputType === "email" && + !this.validators.some((v) => v.getType() === "emailvalidator") && + emailValidator.validate(this.value, name) + ) { + const maxError = new CustomError( + emailValidator.getErrorText(name), + this + ); + errors.push(maxError); + } } + protected canSetValueToSurvey(): boolean { if (!this.isMinMaxType) return true; const isValid = !this.isValueLessMin && !this.isValueGreaterMax; diff --git a/src/validator.ts b/src/validator.ts index 8f8474755f..8cbfc9b97e 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -38,7 +38,7 @@ export class SurveyValidator extends Base { get locText(): LocalizableString { return this.getLocalizableString("text"); } - protected getErrorText(name: string): string { + public getErrorText(name: string): string { if (this.text) return this.text; return this.getDefaultErrorText(name); } diff --git a/tests/surveyquestiontests.ts b/tests/surveyquestiontests.ts index 23cb90d12d..02747113d0 100644 --- a/tests/surveyquestiontests.ts +++ b/tests/surveyquestiontests.ts @@ -5116,6 +5116,29 @@ QUnit.test("select items and then set maxSelectedChoices in checkbox", function assert.equal(question.otherItem.isEnabled, false, "otherItem is disabled"); }); +QUnit.test("select items and then set minSelectedChoices in checkbox", function (assert) { + var survey = new SurveyModel({ + elements: [ + { + type: "checkbox", + name: "q1", + choices: [1, 2, 3, 4, 5], + hasSelectAll: true, + hasOther: true, + }, + ], + }); + var question = survey.getQuestionByName("q1"); + question.minSelectedChoices = 3; + question.value = [2, 3]; + question.validate(); + assert.equal(question.hasErrors(), true, "has errors"); + + question.value = [2, 3, 4]; + question.validate(); + assert.equal(question.hasErrors(), false, "has no errors"); +}); + QUnit.test("Matrix Question: columns with true/false values", function (assert) { var matrix = new QuestionMatrixModel("q1"); matrix.columns = [true, false, 0, "0", 1];