Skip to content

Commit

Permalink
New Error mode & page navigations fix #7411 (#7417)
Browse files Browse the repository at this point in the history
* New Error mode & page navigations fix #7411

* Change API #7411

* Describe the new validation properties and update the Data Validation help topic

* Fix a typo

* Remove unneeded option #7411

---------

Co-authored-by: RomanTsukanov <sergeich16@gmail.com>
  • Loading branch information
andrewtelnov and RomanTsukanov committed Nov 28, 2023
1 parent 1749439 commit 268e3f8
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 28 deletions.
53 changes: 43 additions & 10 deletions docs/data-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,18 @@ description: SurveyJS lets you validate user responses before users proceed to t
---
# Data Validation

SurveyJS Form Library allows you to validate user responses on the client or server side. Regardless of the type, validation activates before a user proceeds to the next page or completes the survey. If the current page contains errors, the survey indicates them and focuses the first question with an invalid answer. If you want to run validation immediately after a user answers a question, set the Survey's [`checkErrorsMode`](https://surveyjs.io/Documentation/Library?id=surveymodel#checkErrorsMode) property to `"onValueChanged"`:
Data validation ensures that respondents fill out all required form fields and the format of values is correct before they are submitted to the server. SurveyJS Form Library supports data validation on the client and server side and allows you to validate user responses immediately after an answer is entered or when respondents proceed to the next page or end the survey. Refer to the following sections for information on how to add and configure data validation in your survey:

- [Enable Immediate Data Validation](#enable-immediate-data-validation)
- [Built-In Client-Side Validators](#built-in-client-side-validators)
- [Implement Custom Client-Side Validation](#implement-custom-client-side-validation)
- [Server-Side Validation](#server-side-validation)
- [Postpone Validation Until Survey Ends](#postpone-validation-until-survey-ends)
- [Switch Between Pages with Validation Errors](#switch-between-pages-with-validation-errors)

## Enable Immediate Data Validation

By default, data validation activates when a respondent proceeds to the next page. If the current page contains errors, the survey indicates them and focuses the first question with an invalid answer. If you want to run validation immediately after a respondent answers a question, set the Survey's [`checkErrorsMode`](https://surveyjs.io/Documentation/Library?id=surveymodel#checkErrorsMode) property to `"onValueChanged"`.

```js
const surveyJson = {
Expand All @@ -15,11 +26,7 @@ const surveyJson = {
}
```

Refer to the sections below for information on how to enable validation in your survey:

- [Built-In Client-Side Validators](#built-in-client-side-validators)
- [Implement Custom Client-Side Validation](#implement-custom-client-side-validation)
- [Server-Side Validation](#server-side-validation)
Alternatively, you can [postpone data validation](#postpone-validation-until-survey-ends) until a respondent completes the survey.

## Built-In Client-Side Validators

Expand Down Expand Up @@ -74,7 +81,7 @@ The following class-based validators are available:
| `"answercount"` | [`AnswerCountValidator`](https://surveyjs.io/Documentation/Library?id=AnswerCountValidator) | Throws an error if a user selects fewer choices than specified by [`minCount`](https://surveyjs.io/Documentation/Library?id=AnswerCountValidator#minCount) or more choices than specified by [`maxCount`](https://surveyjs.io/Documentation/Library?id=AnswerCountValidator#maxCount). Applies only to question types that can have multiple values (for instance, [Checkbox](https://surveyjs.io/Documentation/Library?id=questioncheckboxmodel)). |
| `"regex"` | [`RegexValidator`](https://surveyjs.io/Documentation/Library?id=RegexValidator) | Throws an error if an entered value does not match a regular expression defined in the [`regex`](https://surveyjs.io/Documentation/Library?id=RegexValidator#regex) property. |

[View Demo](https://surveyjs.io/Examples/Library?id=validators-standard (linkStyle))
[View Demo](https://surveyjs.io/form-library/examples/javascript-form-validation/ (linkStyle))

## Implement Custom Client-Side Validation

Expand Down Expand Up @@ -128,7 +135,7 @@ const surveyJson = {
};
```

[View Demo](https://surveyjs.io/Examples/Library?id=validators-custom (linkStyle))
[View Demo](https://surveyjs.io/form-library/examples/add-custom-survey-data-validation/ (linkStyle))

## Server-Side Validation

Expand Down Expand Up @@ -176,7 +183,7 @@ function validateCountry(survey, { data, errors, complete }) {
survey.onServerValidateQuestions.add(validateCountry);
```

[View Demo](https://surveyjs.io/Examples/Library?id=validators-server (linkStyle))
[View Demo](https://surveyjs.io/form-library/examples/javascript-server-side-form-validation/ (linkStyle))

Alternatively, you can use [expressions](https://surveyjs.io/Documentation/Library?id=design-survey-conditional-logic#expressions) to implement custom validation. Create an [asynchronous function](https://surveyjs.io/Documentation/Library?id=design-survey-conditional-logic#asynchronous-functions), register it, and then call it within your expression. The following code uses this technique to implement the previously demonstrated validation scenario:

Expand Down Expand Up @@ -217,7 +224,33 @@ const surveyJson = {
};
```

[View Demo](https://surveyjs.io/form-library/examples/validators-async-expression/reactjs (linkStyle))
[View Demo](https://surveyjs.io/form-library/examples/javascript-async-form-validation/ (linkStyle))

## Postpone Validation Until Survey Ends

Your survey can trigger data validation when a respondent clicks the Complete button. If the survey contains validation errors, it will take the respondent to the page with the first error and focus the question with the invalid answer. To activate this behavior, set the `checkErrorsMode` property to `"onComplete"`:

```js
const surveyJson = {
"checkErrorsMode": "onComplete",
"elements": [
// ...
]
}
```

## Switch Between Pages with Validation Errors

By default, a respondent cannot leave a page that contains validation errors. If you want to let a respondent switch between pages regardless of whether they have validation errors or not, enable the [`validationAllowSwitchPages`](https://surveyjs.io/form-library/documentation/api-reference/survey-data-model#validationAllowSwitchPages) property.

```js
const surveyJson = {
"validationAllowSwitchPages": true,
"elements": [
// ...
]
}
```

## See Also

Expand Down
49 changes: 36 additions & 13 deletions src/survey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1530,7 +1530,8 @@ export class SurveyModel extends SurveyElementCore
* Specifies whether to hide validation errors thrown by the Required validation in the UI.
*
* [Built-In Client-Side Validators](https://surveyjs.io/form-library/documentation/data-validation#built-in-client-side-validators (linkStyle))
* @see ignoreValidation
* @see validationEnabled
* @see validationAllowSwitchPages
*/
public hideRequiredErrors: boolean = false;
beforeSettingQuestionErrors(
Expand Down Expand Up @@ -1647,6 +1648,10 @@ export class SurveyModel extends SurveyElementCore
* - `"onComplete"` - Triggers validation when a user clicks the Complete button. If previous pages contain errors, the survey switches to the page with the first error.
*
* Refer to the following help topic for more information: [Data Validation](https://surveyjs.io/form-library/documentation/data-validation).
* @see validationEnabled
* @see validationAllowSwitchPages
* @see validationAllowComplete
* @see validate
*/
public get checkErrorsMode(): string {
return this.getPropertyValue("checkErrorsMode");
Expand Down Expand Up @@ -3537,15 +3542,32 @@ export class SurveyModel extends SurveyElementCore
document.cookie = this.cookieName + "=;";
}
/**
* Specifies whether to skip validation when you switch between pages or complete the survey programmatically or when users do that in the UI.
* This property is obsolete. Use the [`validationEnabled`](https://surveyjs.io/form-library/documentation/api-reference/survey-data-model#validationEnabled) property instead.
*/
public get ignoreValidation(): boolean { return !this.validationEnabled; }
public set ignoreValidation(val: boolean) { this.validationEnabled = !val; }
/**
* Specifies whether data validation is enabled.
*
* Default value: `false`
* Default value: `true`
* @see checkErrorsMode
* @see hideRequiredErrors
* @see nextPage
* @see isPrevPage
* @see completeLastPage
*/
public ignoreValidation: boolean = false;
public validationEnabled: boolean = true;
/**
* Specifies whether respondents can switch the current page even if it contains validation errors.
*
* Default value: `false`
* @see checkErrorsMode
*/
public validationAllowSwitchPages: boolean = false;
/**
* Specifies whether respondents can end a survey with validation errors.
*
* Default value: `false`
* @see checkErrorsMode
*/
public validationAllowComplete: boolean = false;
/**
* Switches the survey to the next page.
*
Expand All @@ -3560,17 +3582,18 @@ export class SurveyModel extends SurveyElementCore
return this.doCurrentPageComplete(false);
}
private hasErrorsOnNavigate(doComplete: boolean): boolean {
if (this.ignoreValidation || !this.isEditMode) return false;
var func = (hasErrors: boolean) => {
if (!hasErrors) {
if (!this.isEditMode || this.ignoreValidation) return false;
const skipValidation = doComplete && this.validationAllowComplete || !doComplete && this.validationAllowSwitchPages;
const func = (hasErrors: boolean) => {
if (!hasErrors || skipValidation) {
this.doCurrentPageCompleteCore(doComplete);
}
};
if (this.isValidateOnComplete) {
if (!this.isLastPage) return false;
return this.validate(true, true, func) !== true;
return this.validate(true, true, func) !== true && !skipValidation;
}
return this.validateCurrentPage(func) !== true;
return this.validateCurrentPage(func) !== true && !skipValidation;
}
private asyncValidationQuesitons: Array<Question>;
private checkForAsyncQuestionValidation(
Expand Down Expand Up @@ -4845,7 +4868,7 @@ export class SurveyModel extends SurveyElementCore
return this.checkErrorsMode === "onValueChanged";
}
private get isValidateOnComplete(): boolean {
return this.checkErrorsMode === "onComplete";
return this.checkErrorsMode === "onComplete" || this.validationAllowSwitchPages && !this.validationAllowComplete;
}
matrixCellValidate(question: QuestionMatrixDropdownModelBase, options: MatrixCellValidateEvent): SurveyError {
options.question = question;
Expand Down
57 changes: 52 additions & 5 deletions tests/surveytests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,19 +482,66 @@ QUnit.test("Do not run triggers in display mode", function (assert) {
assert.equal(survey.state, "running", "survey is running");
assert.equal(survey.getValue("question3"), undefined, "question3 value is not set");
});
QUnit.test("Do not show errors if survey.ignoreValidation = true", function (
QUnit.test("Do not show errors if ignoreValidation = true", function (
assert
) {
var survey = twoPageSimplestSurvey();

(<Question>survey.pages[0].questions[0]).isRequired = true;
(<Question>survey.pages[1].questions[0]).isRequired = true;
const survey = twoPageSimplestSurvey();
const q1 = survey.pages[0].questions[0];
q1.isRequired = true;
survey.pages[1].questions[0].isRequired = true;
assert.equal(survey.validationEnabled, true, "validationEnabled #1");
assert.equal(survey.ignoreValidation, false, "ignoreValidation #1");
survey.ignoreValidation = true;
assert.equal(survey.validationEnabled, false, "validationEnabled #2");
assert.equal(survey.ignoreValidation, true, "ignoreValidation #2");
survey.validationEnabled = true;
assert.equal(survey.validationEnabled, true, "validationEnabled #3");
assert.equal(survey.ignoreValidation, false, "ignoreValidation #3");
survey.validationEnabled = false;
assert.equal(survey.validationEnabled, false, "validationEnabled #4");
assert.equal(survey.ignoreValidation, true, "ignoreValidation #4");

survey.nextPage();
assert.equal(q1.errors.length, 0, "There is a required error");
assert.equal(survey.currentPageNo, 1, "Can move into another page");
survey.completeLastPage();
assert.equal(survey.state, "completed", "Can complete survey with erros");
});
QUnit.test("Show error, but allow to navigate if validationAllowSwitchPages = true and validationAllowComplete=true", function (assert) {
const survey = new SurveyModel({
checkErrorsMode: "onValueChanged",
pages: [
{ elements: [{ type: "text", name: "q1", inputType: "number", max: 10 }] },
{ elements: [{ type: "text", name: "q2" }] }
]
});
survey.validationAllowSwitchPages = true;
survey.validationAllowComplete = true;
const q1 = survey.getQuestionByName("q1");
q1.value = 12;
assert.equal(q1.errors.length, 1, "There is an error");
survey.nextPage();
assert.equal(survey.currentPageNo, 1, "Can move into another page");
survey.completeLastPage();
assert.equal(survey.state, "completed", "Can complete survey with erros");
});
QUnit.test("Show error, but allow to navigate if survey.validationAllowSwitchPages = true", function (assert) {
const survey = new SurveyModel({
checkErrorsMode: "onValueChanged",
pages: [
{ elements: [{ type: "text", name: "q1", inputType: "number", max: 10 }] },
{ elements: [{ type: "text", name: "q2" }] }
]
});
survey.validationAllowSwitchPages = true;
const q1 = survey.getQuestionByName("q1");
q1.value = 12;
assert.equal(q1.errors.length, 1, "There is an error");
survey.nextPage();
assert.equal(survey.currentPageNo, 1, "Can move into another page");
survey.completeLastPage();
assert.equal(survey.currentPageNo, 0, "Move to the first page");
});
QUnit.test("Check pages state on onValueChanged event", function (assert) {
var survey = new SurveyModel({
pages: [
Expand Down

0 comments on commit 268e3f8

Please sign in to comment.