Skip to content

Commit

Permalink
Merge branch 'master' of github.com:surveyjs/survey-library into issu…
Browse files Browse the repository at this point in the history
…e/4842-input-mask
  • Loading branch information
OlgaLarina committed Nov 28, 2023
2 parents e16d5ab + 5ce57ee commit c8dbb90
Show file tree
Hide file tree
Showing 46 changed files with 515 additions and 109 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
3 changes: 1 addition & 2 deletions docs/design-survey-conditional-logic.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ Returns the sum of passed numbers.
*Example*: `"expression": "sum({total1}, {total2})"`
[View Source Code](https://github.com/surveyjs/survey-library/blob/68eb0054dc83d2f45a6daa1042bf7440c8faf007/src/functionsfactory.ts#L247-L250 (linkStyle))
[View Source Code](https://github.com/surveyjs/survey-library/blob/68eb0054dc83d2f45a6daa1042bf7440c8faf007/src/functionsfactory.ts#L73-L82 (linkStyle))
---
Expand Down Expand Up @@ -388,7 +388,6 @@ Returns the sum of numbers taken from a specified data field. This data field is
*Example*: `"expression": "sumInArray({matrixdynamic}, 'total') > 1000"`
[View Source Code](https://github.com/surveyjs/survey-library/blob/68eb0054dc83d2f45a6daa1042bf7440c8faf007/src/functionsfactory.ts#L164-L171 (linkStyle))
[View Demo](https://surveyjs.io/Examples/Library?id=questiontype-expression#content-js (linkStyle))
---
Expand Down
1 change: 1 addition & 0 deletions packages/survey-angular-ui/src/page.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<div *ngIf="model._showDescription" [class]="model.cssClasses.page.description">
<sv-ng-string [model]="model.locDescription"></sv-ng-string>
</div>
<div *ngIf="model.hasVisibleErrors" [element]="model" sv-ng-errors></div>
<ng-container *ngFor="let row of model.rows">
<ng-template [component]="{ name: $any(model.survey).getRowWrapperComponentName(row), data: { componentData: $any(model.survey).getRowWrapperComponentData(row) } }">
<sv-ng-row [row]="row"></sv-ng-row>
Expand Down
1 change: 1 addition & 0 deletions packages/survey-vue3-ui/src/Page.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<div v-if="showDescription" :class="page.cssClasses.page.description">
<survey-string :locString="page.locDescription" />
</div>
<survey-errors :element="page" />
<template v-for="(row, index) in rows" :key="page.id + '_' + index">
<component
:is="(page.getSurvey() as SurveyModel).getRowWrapperComponentName(row)"
Expand Down
18 changes: 10 additions & 8 deletions src/defaultCss/defaultV2Css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ export var defaultV2Css = {
nested: "sd-element--nested sd-element--nested-with-borders",
invisible: "sd-element--invisible",
navigationButton: "",
compact: "sd-element--with-frame sd-element--compact"
compact: "sd-element--with-frame sd-element--compact",
errorsContainer: "sd-panel__errbox sd-element__erbox sd-element__erbox--above-element"
},
paneldynamic: {
mainRoot: "sd-element sd-question sd-question--paneldynamic sd-element--complex sd-question--complex sd-row__question",
Expand Down Expand Up @@ -148,7 +149,8 @@ export var defaultV2Css = {
root: "sd-page sd-body__page",
emptyHeaderRoot: "sd-page__empty-header",
title: "sd-title sd-page__title",
description: "sd-description sd-page__description"
description: "sd-description sd-page__description",
errorsContainer: "sd-page__errbox"
},
pageTitle: "sd-title sd-page__title",
pageDescription: "sd-description sd-page__description",
Expand Down Expand Up @@ -203,6 +205,9 @@ export var defaultV2Css = {
invisible: "sd-element--invisible",
composite: "sd-element--complex",
disabled: "sd-question--disabled",
errorsContainer: "sd-element__erbox sd-question__erbox",
errorsContainerTop: "sd-element__erbox--above-element sd-question__erbox--above-question",
errorsContainerBottom: "sd-question__erbox--below-question"
},
image: {
mainRoot: "sd-question sd-question--image",
Expand All @@ -219,14 +224,11 @@ export var defaultV2Css = {
withFrame: ""
},
error: {
root: "sd-question__erbox",
root: "sd-error",
icon: "",
item: "",
outsideQuestion: "sd-question__erbox--outside-question",
aboveQuestion: "sd-question__erbox--above-question",
belowQuestion: "sd-question__erbox--below-question",
locationTop: "sd-question__erbox--location--top",
locationBottom: "sd-question__erbox--location--bottom"
locationTop: "",
locationBottom: ""
},
checkbox: {
root: "sd-selectbase",
Expand Down
6 changes: 3 additions & 3 deletions src/defaultV2-theme/blocks/sd-complex-element.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
padding-top: 0;
}

.sd-element--complex.sd-element--nested-with-borders>.sd-question__erbox--outside-question,
.sd-element--complex.sd-element--with-frame>.sd-question__erbox--outside-question {
.sd-element--complex.sd-element--nested-with-borders>.sd-element__erbox,
.sd-element--complex.sd-element--with-frame>.sd-element__erbox {
margin-top: 0;
margin-bottom: 0;
}
Expand Down Expand Up @@ -49,7 +49,7 @@
padding-bottom: calc(0.5 * var(--sd-base-vertical-padding));
}

&>.sd-question__erbox--outside-question {
&>.sd-element__erbox {
margin-left: calc(-1 * var(--sd-base-padding));
margin-right: calc(-1 * var(--sd-base-padding));
width: calc(100% + 2 * var(--sd-base-padding));
Expand Down
14 changes: 14 additions & 0 deletions src/defaultV2-theme/blocks/sd-error.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.sd-error {
display: block;
padding: calcSize(1) calcSize(1.5);
border-radius: calcCornerRadius(1);
line-height: calcLineHeight(1);
font-size: calcFontSize(0.75);
font-weight: 600;
text-align: left;
color: $red;
white-space: normal;
width: 100%;
background-color: $red-light;
box-sizing: border-box;
}
3 changes: 2 additions & 1 deletion src/defaultV2-theme/blocks/sd-multipletext.scss
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
text-overflow: ellipsis;
white-space: nowrap;
}

.sd-question__content:focus-within .sd-multipletext-item__character-counter {
padding-inline-end: calcSize(8);
}
Expand All @@ -104,7 +105,7 @@
}

.sd-multipletext__cell--error {
.sd-question__erbox--outside-question {
.sd-question__erbox {
margin: 0;
}
}
Expand Down
23 changes: 23 additions & 0 deletions src/defaultV2-theme/blocks/sd-page.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,27 @@

.sd-page .sd-page__description {
@include page_description();
}


.sd-page__errbox {
padding: calc(0.5 * var(--sd-base-vertical-padding) + #{$base-unit}) var(--sd-base-padding);
}

.sd-page__title,
.sd-page__description {
&~.sd-page__errbox {
margin-top: calc(0.5 * var(--sd-base-vertical-padding) + #{$base-unit});
margin-bottom: calcSize(-1);
}
}

.sd-root--compact {

.sd-page__title,
.sd-page__description {
&~.sd-page__errbox {
margin-bottom: 0;
}
}
}
19 changes: 18 additions & 1 deletion src/defaultV2-theme/blocks/sd-panel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
.sd-panel.sd-panel--as-page {
&>.sd-panel__header.sd-panel__header {
padding-top: 0;
padding-bottom: calcSize(3);
padding-bottom: calc(0.5 * var(--sd-base-vertical-padding) + #{$base-unit});

&:after {
content: none;
Expand All @@ -26,13 +26,30 @@
.sd-panel__description {
@include page_description();
}

.sd-panel__required-text {
display: none;
}
}

&>.sd-panel__errbox {
margin: 0 0 calcSize(2) 0;
padding: calc(0.5 * var(--sd-base-vertical-padding) + #{$base-unit}) var(--sd-base-padding);
}

&>.sd-panel__content {
padding-top: 0;
}
}

.sd-root--compact {
.sd-panel--as-page {
&>.sd-panel__errbox {
margin: 0 0 var(--sd-base-vertical-padding) 0;
}
}
}

.sd-row~.sd-row {
.sd-panel--as-page {
padding-top: calcSize(4);
Expand Down
30 changes: 6 additions & 24 deletions src/defaultV2-theme/blocks/sd-question.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,7 @@
position: relative;
}

.sd-question__erbox {
display: block;
padding: calcSize(1) calcSize(1.5);
border-radius: calcCornerRadius(1);
line-height: calcLineHeight(1);
font-size: calcFontSize(0.75);
text-align: left;
color: $red;
font-weight: 600;
white-space: normal;
}

.sd-question__erbox--outside-question {
width: 100%;
color: $red;
background-color: $red-light;
box-sizing: border-box;
}

.sd-question__erbox--above-question {
.sd-element__erbox--above-element {
margin-bottom: calcSize(1);
}

Expand All @@ -38,7 +19,7 @@
margin-top: calc(0.25 * var(--sd-base-vertical-padding) + 0.5 * #{$base-unit});
}

.sd-element--with-frame>.sd-question__erbox--above-question {
.sd-element--with-frame>.sd-element__erbox--above-element {
margin-bottom: var(--sd-base-padding);
border-radius: calcCornerRadius(1) calcCornerRadius(1) 0 0;
}
Expand Down Expand Up @@ -80,12 +61,13 @@

&.sd-question--error-bottom {
padding-bottom: 0;

&>.sd-question__content {
margin-bottom: var(--sd-base-padding)
}
margin-bottom: var(--sd-base-padding)
}
}

&>.sd-question__erbox--outside-question {
&>.sd-element__erbox {
margin-left: calc(-1 * var(--sd-base-padding));
margin-right: calc(-1 * var(--sd-base-padding));
width: calc(100% + 2 * var(--sd-base-padding));
Expand Down
Loading

0 comments on commit c8dbb90

Please sign in to comment.