Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: support minSelectedChoices for ranking(selectToRank) and checkboxes #6612

Merged
merged 6 commits into from
Jul 27, 2023
47 changes: 46 additions & 1 deletion src/question_checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -168,11 +170,12 @@ export class QuestionCheckboxModel extends QuestionCheckboxBase {
return !this.valuePropertyName ? val : val[this.valuePropertyName];
}
/**
* Sets a limit on the number of selected choices.
* Specifies the maximum 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.
* @see minSelectedChoices
*/
public get maxSelectedChoices(): number {
return this.getPropertyValue("maxSelectedChoices");
Expand All @@ -182,6 +185,21 @@ export class QuestionCheckboxModel extends QuestionCheckboxBase {
this.setPropertyValue("maxSelectedChoices", val);
this.filterItems();
}
/**
* Specifies the minimum 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 `minSelectedChoices` value.
* @see maxSelectedChoices
*/
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
Expand Down Expand Up @@ -215,6 +233,23 @@ export class QuestionCheckboxModel extends QuestionCheckboxBase {
const val = this.renderedValue as Array<any>;
return val.map((item: any) => new ItemValue(item));
}

protected onCheckForErrors(
errors: Array<SurveyError>,
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);
Expand Down Expand Up @@ -242,6 +277,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;
Expand Down Expand Up @@ -439,6 +482,7 @@ export class QuestionCheckboxModel extends QuestionCheckboxBase {
json["type"] = "radiogroup";
}
json["maxSelectedChoices"] = 0;
json["minSelectedChoices"] = 0;
return json;
}
public isAnswerCorrect(): boolean {
Expand Down Expand Up @@ -537,6 +581,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",
Expand Down
1 change: 1 addition & 0 deletions src/question_ranking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
28 changes: 17 additions & 11 deletions src/question_text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -71,16 +71,7 @@ export class QuestionTextModel extends QuestionTextBase {
this.setRenderedMinMax(values, properties);
}
}
public getValidators(): Array<SurveyValidator> {
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;
}
Expand Down Expand Up @@ -257,7 +248,22 @@ export class QuestionTextModel extends QuestionTextBase {
); };
errors.push(maxError);
}

var name = this.name;
dmitry-kurmanov marked this conversation as resolved.
Show resolved Hide resolved
var emailValidator = new EmailValidator();
if (
this.inputType === "email" &&
!this.validators.some((v) => v.getType() === "emailvalidator") &&
emailValidator.validate(this.value, name)
dmitry-kurmanov marked this conversation as resolved.
Show resolved Hide resolved
) {
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;
Expand Down
2 changes: 1 addition & 1 deletion src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
dmitry-kurmanov marked this conversation as resolved.
Show resolved Hide resolved
if (this.text) return this.text;
return this.getDefaultErrorText(name);
}
Expand Down
23 changes: 23 additions & 0 deletions tests/surveyquestiontests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <QuestionCheckboxModel>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];
Expand Down