form-father 0.2.8 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +223 -9
- package/RESPONSE_API.md +233 -0
- package/dist/FormFather.min.js +2 -0
- package/{index.d.ts → dist/index.d.ts} +97 -12
- package/{index.js → dist/index.js} +334 -74
- package/{index.js.map → dist/index.js.map} +1 -1
- package/{types → dist/types}/index.d.ts +37 -13
- package/package.json +20 -3
- package/FormFather.min.js +0 -2
- /package/{FormFather.min.js.map → dist/FormFather.min.js.map} +0 -0
- /package/{types → dist/types}/helpers.d.ts +0 -0
- /package/{types → dist/types}/validators.d.ts +0 -0
package/README.md
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/form-father)
|
|
4
4
|
[](https://www.npmjs.com/package/form-father)
|
|
5
5
|
|
|
6
|
+
- [🇬🇧 English documentation](https://github.com/Poliklot/form-father/blob/master/docs/en/README.md)
|
|
7
|
+
|
|
6
8
|
**Form Father** — это библиотека для обработки форм на чистом JavaScript, обеспечивающая удобную валидацию и отправку
|
|
7
9
|
форм с использованием TypeScript.
|
|
8
10
|
|
|
@@ -16,13 +18,31 @@ npm install form-father
|
|
|
16
18
|
|
|
17
19
|
## Использование
|
|
18
20
|
|
|
21
|
+
```html
|
|
22
|
+
<form
|
|
23
|
+
novalidate
|
|
24
|
+
onsubmit="return false"
|
|
25
|
+
method="post"
|
|
26
|
+
action="/callback.php"
|
|
27
|
+
enctype="multipart/form-data"
|
|
28
|
+
>
|
|
29
|
+
|
|
30
|
+
<!-- inputs -->
|
|
31
|
+
|
|
32
|
+
<button type="submit">
|
|
33
|
+
Отправить
|
|
34
|
+
</button>
|
|
35
|
+
</form>
|
|
36
|
+
```
|
|
37
|
+
|
|
19
38
|
```javascript
|
|
20
|
-
import Form from 'form-father';
|
|
39
|
+
import Form, { isUrlValid, serializeToFormData } from 'form-father';
|
|
21
40
|
|
|
22
41
|
Form.setDefaultParams({
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
42
|
+
showLoaderButton: false,
|
|
43
|
+
scrollToFirstErroredInput: false,
|
|
44
|
+
logging: false,
|
|
45
|
+
loaderColor: 'currentColor',
|
|
26
46
|
});
|
|
27
47
|
|
|
28
48
|
const formElement = document.querySelector('#myForm');
|
|
@@ -32,29 +52,54 @@ const options = {
|
|
|
32
52
|
},
|
|
33
53
|
onResponse: (responseBody, formInstance) => {
|
|
34
54
|
// Действия при получении ответа от сервера
|
|
55
|
+
formInstance.clearInputs();
|
|
35
56
|
},
|
|
36
57
|
// Другие опции...
|
|
37
58
|
};
|
|
38
59
|
|
|
39
60
|
const form = new Form(formElement, options);
|
|
61
|
+
const simpleForm = new Form(document.querySelector('#simpleForm')); // options можно не передавать
|
|
40
62
|
```
|
|
41
63
|
|
|
64
|
+
```ts
|
|
65
|
+
import Form, { type FormOptions, type ValidationSchema, type ResponseBody } from 'form-father';
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Формат ответов сервера
|
|
69
|
+
|
|
70
|
+
Данная библиотека абстрагирует процесс парсинга HTTP-ответов и выполняет действия на их основе. Для её корректной работы
|
|
71
|
+
сервер должен возвращать ответы в строго определённом формате. Полное описание формата (схемы) ответа от сервера
|
|
72
|
+
представлено в данном техническом документе:
|
|
73
|
+
[Спецификация формата ответов API.](https://github.com/Poliklot/form-father/blob/master/RESPONSE_API.md)
|
|
74
|
+
|
|
42
75
|
## Опции
|
|
43
76
|
|
|
44
77
|
- **onSubmit**: Функция обратного вызова, вызываемая при отправке формы.
|
|
45
78
|
- **onResponse**: Функция обратного вызова при получении ответа от сервера.
|
|
46
|
-
- **onResponseSuccess**: Функция вызывается при успешном
|
|
47
|
-
- **onResponseUnsuccess**: Функция вызывается при неуспешном
|
|
79
|
+
- **onResponseSuccess**: Функция вызывается при успешном HTTP-ответе и `success: true`.
|
|
80
|
+
- **onResponseUnsuccess**: Функция вызывается при неуспешном HTTP-ответе, `success !== true` или некорректном JSON.
|
|
48
81
|
- **showLoaderButton**: Показывать ли лоадер в кнопке отправки. По умолчанию `true`.
|
|
49
82
|
- **scrollToFirstErroredInput**: Прокручивать ли к первому полю с ошибкой. По умолчанию `true`.
|
|
50
83
|
- **customTypeError**: Кастомный тип ошибки.
|
|
51
84
|
- **loaderColor**: Цвет лоадера в кнопке отправки.
|
|
52
85
|
- **logging**: Нужно ли выводить данные в консоль. По умолчанию `false`.
|
|
53
|
-
|
|
86
|
+
|
|
87
|
+
## Отправка формы
|
|
88
|
+
|
|
89
|
+
- Submit-элементами считаются `button[type="submit"]`, `button` без `type`, `input[type="submit"]` и
|
|
90
|
+
`input[type="image"]`.
|
|
91
|
+
- Для `method="GET"` и `method="HEAD"` данные добавляются в query string `action`, тело запроса не отправляется.
|
|
92
|
+
- `wrapData` применяется для всех поддержанных `enctype`: `application/x-www-form-urlencoded`, `multipart/form-data`,
|
|
93
|
+
`text/plain`, `application/json`.
|
|
94
|
+
- `onResponseSuccess` вызывается только при успешном HTTP-ответе и `success: true`.
|
|
95
|
+
- `onResponseUnsuccess` вызывается при неуспешном HTTP-ответе, `success !== true` или некорректном JSON-ответе.
|
|
54
96
|
|
|
55
97
|
## Методы
|
|
56
98
|
|
|
57
99
|
- **clearInputs()**: Очищает все поля ввода формы.
|
|
100
|
+
- **setDefaultParams(params)**: Метод setDefaultParams используется для установки значений по умолчанию для всех
|
|
101
|
+
экземпляров формы. Эти параметры можно переопределить при инициализации конкретной формы.
|
|
102
|
+
- **destroy()**: Очищает инициализацию формы.
|
|
58
103
|
|
|
59
104
|
## Хелперы
|
|
60
105
|
|
|
@@ -62,11 +107,180 @@ const form = new Form(formElement, options);
|
|
|
62
107
|
|
|
63
108
|
- **serializeToFormData($element)**: Сериализует данные формы в объект `FormData`.
|
|
64
109
|
- **isEmailValid(value)**: Проверяет, является ли строка валидным адресом электронной почты.
|
|
65
|
-
- **isUrlValid(value)**:
|
|
110
|
+
- **isUrlValid(value)**: Проверяет `http(s)`-адрес, домен, IP, `localhost`, схема может быть опущена.
|
|
66
111
|
- **isPhoneValid(value)**: Проверяет, является ли строка валидным номером телефона.
|
|
67
112
|
- **closest($el, selector)**: Находит ближайший родительский элемент по заданному селектору.
|
|
68
113
|
- **blockScrollBody()**: Блокирует прокрутку страницы.
|
|
69
114
|
- **unblockScrollBody()**: Разблокирует прокрутку страницы.
|
|
70
115
|
- **parseCommonResponseProperties(responseBody)**: Обрабатывает общие свойства ответа сервера.
|
|
116
|
+
- **serializeFormToJSON(form)**: Сериализует данные формы в обычный объект.
|
|
117
|
+
|
|
118
|
+
## Валидаторы
|
|
119
|
+
|
|
120
|
+
Form Father поставляется с базовым набором правил валидации, а также позволяет легко добавлять и переопределять
|
|
121
|
+
собственные.
|
|
122
|
+
|
|
123
|
+
### Базовые правила
|
|
124
|
+
|
|
125
|
+
| Ключ | Проверка | Сообщение по умолчанию |
|
|
126
|
+
| ------------- | ------------------------------- | ---------------------- |
|
|
127
|
+
| `required` | значение непустое | **Пустое значение** |
|
|
128
|
+
| `email` | валидный e-mail | **Неверный формат** |
|
|
129
|
+
| `tel` | российский номер `+7XXXXXXXXXX` | **Неверный формат** |
|
|
130
|
+
| `url` | домен/IP/localhost, схема optional | **Неверный формат** |
|
|
131
|
+
| `not-numbers` | строка без цифр | **Неверный формат** |
|
|
132
|
+
|
|
133
|
+
> [!NOTE] Полный список валидаторов можно получить так:
|
|
134
|
+
>
|
|
135
|
+
> ```js
|
|
136
|
+
> import { getAllValidators } from 'form-father';
|
|
137
|
+
>
|
|
138
|
+
> const all = getAllValidators();
|
|
139
|
+
> console.log([...all.keys()]);
|
|
140
|
+
> ```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
### Глобальная схема (`Form.defaultValidationSchema`)
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
Form.defaultValidationSchema = {
|
|
148
|
+
email: {
|
|
149
|
+
selector: 'input[type="email"]', // CSS-селектор, по которому выбираются inputs
|
|
150
|
+
rules: ['required', 'email'],
|
|
151
|
+
},
|
|
152
|
+
tel: {
|
|
153
|
+
selector: 'input[type="tel"]',
|
|
154
|
+
rules: ['required', 'tel'],
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
- **selector** — произвольный CSS. Если он указан, правила применяются ко всем элементам, соответствующим этому
|
|
160
|
+
селектору. Если селектора нет, библиотека использует атрибут `data-validate`.
|
|
161
|
+
- Ту же схему можно переопределить/дополнить локально через `validationSchema` при инициализации формы.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
### Локальная схема (`validationSchema`)
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
const form = new Form($form, {
|
|
169
|
+
validationSchema: {
|
|
170
|
+
tel: {
|
|
171
|
+
// ключ произвольный
|
|
172
|
+
selector: '[data-validate="tel"]',
|
|
173
|
+
rules: ['tel', 'begin+79277'],
|
|
174
|
+
messages: {
|
|
175
|
+
tel: 'Телефон в формате +7…',
|
|
176
|
+
'begin+79277': 'Номер должен начинаться на +79277',
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
_Порядок применения для одного `<input>`:_
|
|
184
|
+
|
|
185
|
+
1. `required` (если атрибут `required`)
|
|
186
|
+
2. правила из `validationSchema`
|
|
187
|
+
3. правила из `data-custom-validate`
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
### Правило через `data-custom-validate`
|
|
192
|
+
|
|
193
|
+
```html
|
|
194
|
+
<input name="code" data-custom-validate="only-digits:6" required />
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
- Если правило неизвестно, в консоль выводится `[FormFather] Unknown validation rule "foo"`.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
### Собственный валидатор
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { registerValidator } from 'form-father';
|
|
205
|
+
|
|
206
|
+
registerValidator(
|
|
207
|
+
'only-digits',
|
|
208
|
+
(v, _input, _form, len) => new RegExp(`^\d{${len}}$`).test(v),
|
|
209
|
+
'Допустимы только цифры',
|
|
210
|
+
);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Теперь можно использовать:
|
|
214
|
+
|
|
215
|
+
```html
|
|
216
|
+
<input name="code" data-custom-validate="only-digits:6" required />
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### Переопределение валидатора
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
registerValidator(
|
|
225
|
+
'tel',
|
|
226
|
+
v => /^8\d{10}$/.test(v), // новая проверка
|
|
227
|
+
'Номер должен начинаться на 8…', // новое сообщение
|
|
228
|
+
{ override: true }, // ← обязательно!
|
|
229
|
+
);
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
_Без `override: true` библиотека оставит старую версию и выведет warn._
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
### Изменение только текста ошибки
|
|
237
|
+
|
|
238
|
+
#### Глобально
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
const vd = getValidator('email');
|
|
242
|
+
if (vd) vd.defaultMessage = 'Некорректный e-mail';
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### Локально для формы
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
validationSchema: {
|
|
249
|
+
email: {
|
|
250
|
+
selector: 'input[type="email"]',
|
|
251
|
+
rules: ['email'],
|
|
252
|
+
messages: { email: 'Некорректный e-mail' },
|
|
253
|
+
},
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
### Итоговый порядок проверки
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
required → schema.rules → data-custom-validate
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
- Для каждого поля отображается **только первое** найденное сообщение об ошибке.
|
|
266
|
+
- Неизвестные правила логируются и пропускаются до начала проверки, поэтому не влияют на результат валидации.
|
|
267
|
+
|
|
268
|
+
## Сборка и разработка
|
|
269
|
+
|
|
270
|
+
Для сборки проекта используется **Rollup**. Основные команды:
|
|
271
|
+
|
|
272
|
+
- **npm run build**: Сборка проекта.
|
|
273
|
+
- **npm run watch**: Сборка в режиме наблюдения.
|
|
274
|
+
- **npm run demos**: Запуск демо-версии с использованием Vite.
|
|
275
|
+
|
|
276
|
+
## Вклад
|
|
277
|
+
|
|
278
|
+
Будем рады вашим предложениям и улучшениям! Пожалуйста, создавайте
|
|
279
|
+
[issues](https://github.com/poliklot/form-father/issues) и отправляйте
|
|
280
|
+
[pull requests](https://github.com/poliklot/form-father/pulls).
|
|
281
|
+
|
|
282
|
+
## Лицензия
|
|
283
|
+
|
|
284
|
+
[MIT](LICENSE)
|
|
71
285
|
|
|
72
|
-
©
|
|
286
|
+
© 2025 Poliklot
|
package/RESPONSE_API.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
## Краткое введение
|
|
2
|
+
|
|
3
|
+
Эй, ребята! Собрались здесь для того, чтобы понять, как у нас отвечает сервер после того, как отправляем формы через
|
|
4
|
+
AJAX. Это будет не какая-то скучная докуха, а всё по-кубански, с размахом и только по сути!
|
|
5
|
+
|
|
6
|
+
## Что и как 🛠️
|
|
7
|
+
|
|
8
|
+
### `success`
|
|
9
|
+
|
|
10
|
+
- **Тип**: boolean.
|
|
11
|
+
- **Описание**: Флаг, показывающий, всё ли круто.
|
|
12
|
+
- **Как Работает**: Если сервер справился, то `true`, а если нет – `false`.
|
|
13
|
+
|
|
14
|
+
### `redirect-url`
|
|
15
|
+
|
|
16
|
+
- **Тип**: string (опционально).
|
|
17
|
+
- **Описание**: Куда пойти, если всё зашибись.
|
|
18
|
+
- **Когда Появляется**: Только если `success` равно `true`.
|
|
19
|
+
|
|
20
|
+
### `redirect-url-delay`
|
|
21
|
+
|
|
22
|
+
- **Тип**: number (опционально).
|
|
23
|
+
- **Описание**: Задержка в ms перед редиректом.
|
|
24
|
+
|
|
25
|
+
### `reload`
|
|
26
|
+
|
|
27
|
+
- **Тип**: boolean (опционально).
|
|
28
|
+
- **Описание**: Как перезагрузить страничку.
|
|
29
|
+
- **Когда Используем**: Когда сервер скажет "обновись".
|
|
30
|
+
|
|
31
|
+
### `reload-delay`
|
|
32
|
+
|
|
33
|
+
- **Тип**: number (опционально).
|
|
34
|
+
- **Описание**: Задержка в ms перед перезагрузкой страницы.
|
|
35
|
+
|
|
36
|
+
### `error`
|
|
37
|
+
|
|
38
|
+
- **Тип**: boolean (опционально).
|
|
39
|
+
- **Описание**: Флаг о проблемах.
|
|
40
|
+
- **Как Работает**: Если есть косяк, то `true`.
|
|
41
|
+
|
|
42
|
+
### `error-msg`
|
|
43
|
+
|
|
44
|
+
- **Тип**: string (опционально).
|
|
45
|
+
- **Описание**: Пояснения от сервера о косяке.
|
|
46
|
+
- **Пример**: "Тут беда, братишка".
|
|
47
|
+
|
|
48
|
+
### `errors`
|
|
49
|
+
|
|
50
|
+
- **Тип**: array (опционально).
|
|
51
|
+
- **Описание**: Список мелких недоразумений.
|
|
52
|
+
- **Содержание**: Конкретные поля и тексты ошибок.
|
|
53
|
+
|
|
54
|
+
Каждый объект в массиве содержит:
|
|
55
|
+
|
|
56
|
+
- `name` (string): Имя поля, в котором произошла ошибка.
|
|
57
|
+
- `error-msg` (string): Текст ошибки для данного поля.
|
|
58
|
+
|
|
59
|
+
### `error-toast`
|
|
60
|
+
|
|
61
|
+
- Тип: string (опционально).
|
|
62
|
+
- Описание: Выводится всплывашка(toast) с ошибкой.
|
|
63
|
+
- Пример: "Тут беда, братишка".
|
|
64
|
+
|
|
65
|
+
> [!Caution]
|
|
66
|
+
> `error-toast` DEPRECATED!!!
|
|
67
|
+
|
|
68
|
+
### `toast`
|
|
69
|
+
- Тип: объект | массив объектов.
|
|
70
|
+
- Описание: Выводит определённый тип тоста и передаёт опции.
|
|
71
|
+
- Пример, один тост:
|
|
72
|
+
```
|
|
73
|
+
{
|
|
74
|
+
type: 'success',
|
|
75
|
+
title: 'Успешно',
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- Пример, несколько тостов за раз:
|
|
80
|
+
```
|
|
81
|
+
[
|
|
82
|
+
{
|
|
83
|
+
type: 'success',
|
|
84
|
+
title: 'Успешно',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
type: 'error',
|
|
88
|
+
title: 'Ошибка',
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
## Примеры ответов 📝
|
|
95
|
+
|
|
96
|
+
### `Когда всё чик-пук, и можно идти дальше.`
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"success": true,
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### `Если нужен редирект.`
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"success": true,
|
|
109
|
+
"redirect-url": "https://example.com/success",
|
|
110
|
+
"redirect-url-delay": 5000, // если нужна задержка перед редиректом
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### `Если нужна перезагрузка страницы.`
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"success": true,
|
|
119
|
+
"reload": true,
|
|
120
|
+
"reload-delay": 5000, // если нужна задержка перед обновлением страницы
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### `Когда надо что-то менять.`
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"success": false,
|
|
129
|
+
"error": true,
|
|
130
|
+
"error-msg": "Тут беда, братишка",
|
|
131
|
+
"errors": [
|
|
132
|
+
{
|
|
133
|
+
"name": "email",
|
|
134
|
+
"error-msg": "Эй, мэйл какой-то кривой"
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Вот и всё, ребятки! Теперь вы знаете, как наш сервер отзывается на ваши AJAX-формы. Дерзайте!
|
|
141
|
+
|
|
142
|
+
# Как работать с e-commerce
|
|
143
|
+
|
|
144
|
+
- В сниппете #src/snippets/\_ym-metrika.html лежит пример кода инициализации Яндекс.Метрики.
|
|
145
|
+
- В скрипте с id='ecommerceScript' есть 2 важных момента: первый - это пример запуска необходимого кода сразу после
|
|
146
|
+
инициализации Яндекс.метрики, а второй - это функция, которая принимает данные и с учётом этого происходят действия
|
|
147
|
+
необходимые для метрик.
|
|
148
|
+
- На данный момент существует только 4 события: `click`, `submit`, `ajax-success` и `ajax-fail`.
|
|
149
|
+
- Атрибут `data-ecom-data` нужно указывать, только для тех событий, которые не ожидают ответа от сервера.
|
|
150
|
+
|
|
151
|
+
## Примеры использования атрибутов:
|
|
152
|
+
|
|
153
|
+
### Обычный клик на ссылке
|
|
154
|
+
|
|
155
|
+
```html
|
|
156
|
+
<a
|
|
157
|
+
href="#"
|
|
158
|
+
data-ecom-action="click"
|
|
159
|
+
data-ecom-data='{
|
|
160
|
+
"click": {
|
|
161
|
+
"ymCounter123123123": "pull-right",
|
|
162
|
+
"dataLayer": []
|
|
163
|
+
}
|
|
164
|
+
}'
|
|
165
|
+
>
|
|
166
|
+
Оформить заказ
|
|
167
|
+
</a>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Множественное количество событий на Форме-кнопке
|
|
171
|
+
|
|
172
|
+
```html
|
|
173
|
+
<button
|
|
174
|
+
data-form-button
|
|
175
|
+
data-form-button-action="/endpoint"
|
|
176
|
+
data-form-button-method="POST"
|
|
177
|
+
data-form-button-data='{"someProperties": "1234"}'
|
|
178
|
+
data-ecom-action="click,ajax-success,submit"
|
|
179
|
+
data-ecom-data='{
|
|
180
|
+
"click": {
|
|
181
|
+
"ymCounter123123123": "pull-right",
|
|
182
|
+
"dataLayer": []
|
|
183
|
+
},
|
|
184
|
+
"submit": {
|
|
185
|
+
"ymCounter123123123": "pull-right",
|
|
186
|
+
"dataLayer": []
|
|
187
|
+
}
|
|
188
|
+
}'
|
|
189
|
+
>
|
|
190
|
+
Оформить заказ
|
|
191
|
+
</button>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Cобытие у элемента формы
|
|
195
|
+
|
|
196
|
+
```html
|
|
197
|
+
<form
|
|
198
|
+
class="ordering-page__body"
|
|
199
|
+
action="/api/order/"
|
|
200
|
+
method="POST"
|
|
201
|
+
name="ORDER_FORM"
|
|
202
|
+
enctype="multipart/form-data"
|
|
203
|
+
novalidate
|
|
204
|
+
data-ecom-action="ajax-success"
|
|
205
|
+
>
|
|
206
|
+
...
|
|
207
|
+
</form>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
# Сериализация форм и полей
|
|
211
|
+
|
|
212
|
+
## Работа с атрибутом `data-no-serialize-before-changes`
|
|
213
|
+
|
|
214
|
+
### Общее описание
|
|
215
|
+
|
|
216
|
+
Атрибут `data-no-serialize-before-changes` предназначен для контроля сериализации полей формы в AJAX-запросах. С его
|
|
217
|
+
помощью можно указать, какие поля формы не должны быть сериализованы и отправлены на сервер до того момента, как в них
|
|
218
|
+
произойдут изменения со стороны пользователя.
|
|
219
|
+
|
|
220
|
+
### Применение
|
|
221
|
+
|
|
222
|
+
Для использования атрибута достаточно добавить его к любому элементу формы, например, `<input>`, `<select>` или
|
|
223
|
+
`<textarea>`. Пока пользователь не изменит значение такого поля, оно не будет включено в сериализованные данные формы
|
|
224
|
+
при AJAX-запросе.
|
|
225
|
+
|
|
226
|
+
### Пример
|
|
227
|
+
|
|
228
|
+
```html
|
|
229
|
+
<input type="text" name="user-email" data-no-serialize-before-changes />
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
В данном случае, поле для ввода email пользователя не будет сериализовано и отправлено на сервер до тех пор, пока
|
|
233
|
+
пользователь явно не изменит его содержимое.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var FormFather=function(t){"use strict";function e(t,e,r,o){return new(r||(r=Promise))((function(n,i){function s(t){try{l(o.next(t))}catch(t){i(t)}}function a(t){try{l(o.throw(t))}catch(t){i(t)}}function l(t){var e;t.done?n(t.value):(e=t.value,e instanceof r?e:new r((function(t){t(e)}))).then(s,a)}l((o=o.apply(t,e||[])).next())}))}function r(t,e){if(null==t)return null;let r=t;for(;r;){if(r.matches(e))return r;r=r.parentElement}return null}"function"==typeof SuppressedError&&SuppressedError;const o=["http:","https:"];function n(t){return t.length>0&&t.length<=63&&/^[a-z\d-]+$/i.test(t)&&!t.startsWith("-")&&!t.endsWith("-")}function i(t){return!!t&&("localhost"===t||function(t){const e=t.split(".");return 4===e.length&&e.every((t=>{if(!/^\d{1,3}$/.test(t))return!1;const e=Number(t);return e>=0&&e<=255}))}(t)||function(t){if(!t.startsWith("[")||!t.endsWith("]"))return!1;const e=t.slice(1,-1);return e.includes(":")&&/^[\da-f:.]+$/i.test(e)}(t)||function(t){const e=t.split(".");return e.length>=2&&e.every(n)}(t))}function s(t){const e=function(t){const e=t.trim();return 0===e.length?"":e.startsWith("//")?`https:${e}`:/^[a-z][a-z\d+.-]*:\/\//i.test(e)?e:`https://${e}`}(t);if(0===e.length)return!1;try{const t=new URL(e);return o.includes(t.protocol)&&i(t.hostname)}catch(t){return!1}}function a(t){const e="FORM"===t.tagName?Array.from(t.elements):Array.from(t.querySelectorAll("input, select, textarea, button")),r=new FormData;return e.forEach((t=>{if(!(t instanceof HTMLInputElement||t instanceof HTMLSelectElement||t instanceof HTMLTextAreaElement||t instanceof HTMLButtonElement))return;if(!t.name||t.disabled||t.hasAttribute("data-no-serialize"))return;if(t instanceof HTMLButtonElement)return;const{name:e,type:o}=t;if("file"===o)if(t.multiple){const o=t.files;if(o)for(let t=0;t<o.length;t++)r.append(`${e}[${t}]`,o[t])}else{const o=t.files;o&&o[0]&&r.append(e,o[0])}else{let n;if("select-one"===o||"select-multiple"===o){const i=t;if("select-multiple"===o){Array.from(i.selectedOptions).map((t=>t.value)).forEach(((t,o)=>{r.append(`${e}[${o}]`,t)}))}else n=i.value,n&&r.append(e,n)}else if("tel"===o)n=t.value.split(/[\s()-]/).join("").replace(/^8/,"+7");else if("checkbox"===o||"radio"===o){if(!t.checked)return;n=t.value}else if("date"===o){const e=t.getAttribute("data-date-format");n=e?function(t,e){const r=new Date(t),o=t=>t.toString().padStart(2,"0"),n=o(r.getDate()),i=o(r.getMonth()+1),s=r.getFullYear().toString();return e.replace("dd",n).replace("mm",i).replace("yyyy",s)}(t.value,e):t.value}else n=t.value;n&&"select-one"!==o&&"select-multiple"!==o&&r.append(e,n)}})),r}function l(t){if(Object.prototype.hasOwnProperty.call(t,"redirect-url"))if(t["redirect-url-delay"]){const e=Number(t["redirect-url-delay"]);setTimeout((()=>{window.location.href=t["redirect-url"]}),e)}else window.location.href=t["redirect-url"];else if(Object.prototype.hasOwnProperty.call(t,"reload")){if(!0===t.reload)if(t["reload-delay"]){const e=Number(t["reload-delay"]);setTimeout((()=>{window.location.reload()}),e)}else window.location.reload()}else if(Object.prototype.hasOwnProperty.call(t,"toast")){const e=t.toast;(Array.isArray(e)?e:[e]).forEach((t=>{window.showToast(t)}))}else Object.prototype.hasOwnProperty.call(t,"error-toast")&&window.showToast(t["error-toast"])}var c;const d=Symbol.for("FormFather.validators"),u=null!==(c=globalThis[d])&&void 0!==c?c:globalThis[d]=new Map;const p=t=>u.get(t);function h(t,e,r){u.has(t)||u.set(t,{fn:e,defaultMessage:r})}var f;h("required",((t,e,r)=>{if(e&&e.type){if("checkbox"===e.type)return e.checked;if("radio"===e.type){const t=r.querySelectorAll(`input[type="radio"][name="${e.name}"]`);return Array.from(t).some((t=>t.checked))}}return t.trim().length>0}),"Пустое значение"),h("email",(t=>/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)),"Неверный формат"),h("tel",(t=>/^\+7\d{10}$/.test(t)),"Неверный формат"),h("url",(t=>s(t)),"Неверный формат"),h("not-numbers",(t=>!/[0-9]/.test(t)),"Неверный формат");const m=Symbol.for("formfather.shared"),y=null!==(f=globalThis[m])&&void 0!==f?f:globalThis[m]={defaultParams:{},defaultValidationSchema:void 0};let g=0;const b={tel:{selector:'input[type="tel"]',rules:["tel"]},email:{selector:'input[type="email"]',rules:["email"]},required:{selector:"[required]",rules:["required"]},url:{selector:'input[type="url"]',rules:["url"]},"not-numbers":{selector:'input[data-validate="not-numbers"]',rules:["not-numbers"]}};class v{static get defaultParams(){return y.defaultParams}static set defaultParams(t){y.defaultParams=t}static get defaultValidationSchema(){var t;return null!==(t=y.defaultValidationSchema)&&void 0!==t?t:y.defaultValidationSchema=b}static set defaultValidationSchema(t){y.defaultValidationSchema=t}constructor(t,r={}){this.$submits=[],this.waitResponse=!1,this.inputs=null,this._anim={duration:220,easing:"ease",paddingTopOpened:16,iconDuration:700},this.$el=t,this.options=r;this.config=Object.assign({},{onSubmit:()=>{},onResponse:()=>{},onResponseSuccess:()=>{},onResponseUnsuccess:()=>{},customTypeError:null,loaderColor:"#fff",showLoaderButton:!0,scrollToFirstErroredInput:!0,logging:!1,inputSelector:".input",inputWrapperSelector:".input-primary"},v.defaultParams,r),this.$el?(this.$submits=this.findSubmitElements(),this._onSubmitClickHandler=t=>e(this,void 0,void 0,(function*(){var e,r,o,n;t.preventDefault(),null===(r=(e=this.config).onBeforeValidate)||void 0===r||r.call(e,this);const i=yield this.validate();null===(n=(o=this.config).onAfterValidate)||void 0===n||n.call(o,i,this),i&&(yield this.submit())})),this.isCorrectArguments()&&(this.$submits.forEach((t=>{t.addEventListener("click",this._onSubmitClickHandler)})),this.initialization())):console.warn("Empty $el")}getOptions(){return this.options}static setDefaultParams(t){this.defaultParams=Object.assign(Object.assign({},this.defaultParams),t)}findSubmitElements(){return Array.from(this.$el.querySelectorAll('input[type="submit"], input[type="image"], button[type="submit"], button:not([type])'))}initialization(){this.inputs=this.$el.querySelectorAll(this.config.inputSelector),this._onSubmitHandler=t=>e(this,void 0,void 0,(function*(){var e,r,o,n;t.preventDefault(),null===(r=(e=this.config).onBeforeValidate)||void 0===r||r.call(e,this);const i=yield this.validate();null===(n=(o=this.config).onAfterValidate)||void 0===n||n.call(o,i,this),i&&this.submit()})),this.$el.addEventListener("submit",this._onSubmitHandler)}showError(t,e){const o=r(t,this.config.inputWrapperSelector);if(!o)return void console.warn(`Не найдена обёртка для поля с именем: ${t.name||t.type}`);const n=o.showError;if("function"==typeof n)return void n.call(o,e);t.setAttribute("aria-invalid","true"),o.classList.add("input__wrapper--error");let i=o.querySelector("[data-form-father-error]");i||(i=document.createElement("div"),i.setAttribute("data-form-father-error",""),i.setAttribute("role","alert"),t.insertAdjacentElement("afterend",i)),i.textContent=e}showLoader(){this.$submits.forEach((t=>{if(t.classList.add("button--loading"),t.querySelector(".button__loader"))return;const e="form-father-loader-"+g++,r=`\n\t\t\t\t<svg height="38" viewBox="0 0 38 38" width="38" xmlns="http://www.w3.org/2000/svg">\n\t\t\t\t\t<defs>\n\t\t\t\t\t<linearGradient id="${e}" x1="8.042%" x2="65.682%" y1="0%" y2="23.865%">\n\t\t\t\t\t\t<stop offset="0%" stop-color="${this.config.loaderColor}" stop-opacity="0"/>\n\t\t\t\t\t\t<stop offset="63.146%" stop-color="${this.config.loaderColor}" stop-opacity=".631"/>\n\t\t\t\t\t\t<stop offset="100%" stop-color="${this.config.loaderColor}"/>\n\t\t\t\t\t</linearGradient>\n\t\t\t\t\t</defs>\n\t\t\t\t\t<g fill-rule="evenodd" fill="none">\n\t\t\t\t\t<g transform="translate(1 1)">\n\t\t\t\t\t\t<path d="M36 18c0-9.94-8.06-18-18-18" id="Oval-2" stroke-width="5" stroke="url(#${e})"></path>\n\t\t\t\t\t\t<circle cx="36" cy="18" fill="${this.config.loaderColor}" r="1"></circle>\n\t\t\t\t\t</g>\n\t\t\t\t\t</g>\n\t\t\t\t</svg>\n\t\t\t`,o=document.createElement("span");o.innerHTML=r,o.className="button__loader",t.insertAdjacentElement("afterbegin",o),requestAnimationFrame((()=>{o.setAttribute("data-showed","")}))}))}hideLoader(){this.$submits.forEach((t=>{t.classList.remove("button--loading");const e=t.querySelector(".button__loader");e&&(e.classList.remove("active"),e.removeAttribute("data-showed"),setTimeout((()=>{e.remove()}),250))}))}hideError(t){var e;const o=r(t,this.config.inputWrapperSelector)||t.parentElement;t.removeAttribute("aria-invalid"),null===(e=null==o?void 0:o.querySelector("[data-form-father-error]"))||void 0===e||e.remove(),null==o||o.classList.remove("input__wrapper--error"),o&&o.classList.contains("input__wrapper")&&o.getAttribute("data-type-error")}scrollToFirstErroredInput(t){if(t.length>0){const e=Array.from(this.$el.querySelectorAll("input"));let r=null,o=1/0;if(t.forEach((t=>{const n=e.findIndex((e=>e===t));n<o&&(o=n,r=t)})),r){const t=r.getBoundingClientRect(),e=window.pageYOffset||document.documentElement.scrollTop,o=t.top+e-window.innerHeight/2;document.documentElement.setAttribute("data-now-scrolling",""),window.scrollTo({top:o,behavior:"smooth"}),setTimeout((()=>{document.documentElement.removeAttribute("data-now-scrolling")}),800)}}}validate(){return e(this,arguments,void 0,(function*(t=this.$el){const r=Object.assign(Object.assign({},v.defaultValidationSchema||{}),this.config.validationSchema||{}),o=t=>console.warn(`[FormFather] Unknown validation rule "${t}"`),n=[],i=new WeakSet;let s=!0;const a=(t,e=[])=>{var r;const n=[];t.hasAttribute("required")&&n.push("required"),n.push(...e);const i=(null!==(r=t.getAttribute("data-custom-validate"))&&void 0!==r?r:"").split(",").map((t=>t.trim())).filter(Boolean);n.push(...i.map((t=>{const[e,...r]=t.split(":").map((t=>t.trim()));return r.length>0?{rule:e,params:1===r.length?r[0]:r}:e})));const s=[];for(const t of n){const e="string"==typeof t?t:t.rule;p(e)?s.push(t):o(e)}return s},l=(r,o,...i)=>e(this,[r,o,...i],void 0,(function*(e,r,o={}){if(e.hasAttribute("data-no-validate"))return;const i="checkbox"===e.type||"radio"===e.type?!e.checked:0===e.value.trim().length,a=r.some((t=>"string"==typeof t?"required"===t:"required"===t.rule));if(!i||a){for(const i of r){const{rule:r,params:a}="string"==typeof i?{rule:i,params:void 0}:i,l=p(r),c=yield l.fn(e.value,e,t,a),d="object"==typeof c&&null!==c,u=d?!0===c.valid:!!c;if(d&&c.effect&&(yield c.effect({value:e.value,$input:e,$form:t,params:a})),!u){s=!1;const t=c,i=d&&"string"==typeof t.message&&t.message||o[r]||l.defaultMessage;return this.showError(e,i),void n.push(e)}if(d&&c.stopOthers)return}this.hideError(e)}else this.hideError(e)})),c=e=>"radio"===e.type&&e.name?Array.from(t.querySelectorAll('input[type="radio"]')).filter((t=>t.name===e.name)):[];for(const[e,o]of Object.entries(r)){const{selector:r,rules:n=[],messages:s={}}=o,d=r?t.querySelectorAll(r):t.querySelectorAll(`[data-validate="${e}"]`);for(const t of Array.from(d)){if(i.has(t))continue;const e=c(t);e.length>0?(e.forEach((t=>i.add(t))),yield l(t,a(t,n),s)):(i.add(t),yield l(t,a(t,n),s))}}const d=Array.from(t.querySelectorAll("[data-custom-validate]")).filter((t=>!i.has(t)));for(const t of d)i.add(t),yield l(t,a(t,[]));return!s&&this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(n.filter((t=>!t.hasAttribute("data-no-error-scroll")))),s}))}disableSubmit(){this.$submits.forEach((t=>{t.setAttribute("disabled","")}))}enableSubmit(){this.$submits.forEach((t=>{t.removeAttribute("disabled")}))}clearInputs(){var t;null===(t=this.inputs)||void 0===t||t.forEach((t=>{var e;const r=t;"radio"===r.type||"checkbox"===r.type?r.checked=!1:(r.value="",null===(e=r.parentElement)||void 0===e||e.classList.remove("filled"),r.dispatchEvent(new Event("input")))}))}_prefersReducedMotion(){var t;return"undefined"!=typeof window&&(null===(t=window.matchMedia)||void 0===t?void 0:t.call(window,"(prefers-reduced-motion: reduce)").matches)}_canAnimate(t){return!(!t||!("animate"in t)||this._prefersReducedMotion())}_cancelAnimations(t){var e;const r=t;null===(e=null==r?void 0:r.getAnimations)||void 0===e||e.call(r).forEach((t=>t.cancel()))}showErrorForm(t){var e;const r=this.$el.querySelectorAll(this.config.inputWrapperSelector),o=r[r.length-1];let n=this.$el.querySelector(".error-block-under-input__wrapper");!n||o&&o.nextElementSibling!==n?(n=document.createElement("div"),n.className="error-block-under-input__wrapper",n.innerHTML='\n <div class="error-block-under-input error-block-under-input--warning" style="display:grid;grid-template-columns:auto 1fr;gap:8px;align-items:start;">\n <div class="error-block-under-input__icon-wrapper" style="position:relative;width:20px;height:20px;">\n <span class="error-block-under-input__icon" style="display:block;width:20px;height:20px;"></span>\n <span class="error-block-under-input__icon error-block-under-input__icon--animated" style="display:block;position:absolute;inset:0;"></span>\n </div>\n <p class="error-block-under-input__text" style="margin:0;">\n <span class="error-block-under-input__main-text"></span>\n <span class="error-block-under-input__secondary-text"></span>\n </p>\n </div>\n ',Object.assign(n.style,{boxSizing:"border-box",overflow:"hidden",height:"0px",paddingTop:"0px",opacity:"0"}),o?o.insertAdjacentElement("afterend",n):this.$el.appendChild(n)):(n.style.boxSizing="border-box",n.style.overflow="hidden"),n.querySelector(".error-block-under-input__main-text").textContent=t;const i=n.querySelector(".error-block-under-input__secondary-text");i&&(i.textContent=""),null===(e=n.querySelector(".error-block-under-input"))||void 0===e||e.classList.remove("error-block-under-input--success");const s=n.style.height,a=n.style.paddingTop;n.style.height="auto",n.style.paddingTop=`${this._anim.paddingTopOpened}px`;const l=n.clientHeight;if(n.style.height=s||"0px",n.style.paddingTop=a||"0px",parseFloat(n.style.height)===l&&1===parseFloat(n.style.opacity||"1"))this._ringIcon(n);else{if(this._cancelAnimations(n),this._canAnimate(n)){n.animate([{height:`${parseFloat(n.style.height)||0}px`,paddingTop:`${parseFloat(n.style.paddingTop)||0}px`,opacity:parseFloat(n.style.opacity||"0")},{height:`${l}px`,paddingTop:`${this._anim.paddingTopOpened}px`,opacity:1}],{duration:this._anim.duration,easing:this._anim.easing,fill:"forwards"}).addEventListener("finish",(()=>{n.style.height="auto",n.style.paddingTop=`${this._anim.paddingTopOpened}px`,n.style.opacity="1"}))}else n.style.height="auto",n.style.paddingTop=`${this._anim.paddingTopOpened}px`,n.style.opacity="1";this._ringIcon(n)}this.$el.addEventListener("input",(()=>this.hideErrorForm()),{passive:!0,once:!0})}hideErrorForm(){const t=this.$el.querySelectorAll(this.config.inputWrapperSelector),e=t[t.length-1],r=(null==e?void 0:e.nextElementSibling)||this.$el.querySelector(".error-block-under-input__wrapper");if(!r||!r.classList.contains("error-block-under-input__wrapper"))return;if((""===r.style.height||"auto"===r.style.height)&&(r.style.height=`${r.clientHeight}px`),r.style.paddingTop=r.style.paddingTop||`${this._anim.paddingTopOpened}px`,r.style.opacity=r.style.opacity||"1",this._cancelAnimations(r),this._canAnimate(r)){r.animate([{height:`${parseFloat(r.style.height)||r.clientHeight}px`,paddingTop:`${parseFloat(r.style.paddingTop)||this._anim.paddingTopOpened}px`,opacity:parseFloat(r.style.opacity||"1")},{height:"0px",paddingTop:"0px",opacity:0}],{duration:this._anim.duration,easing:this._anim.easing,fill:"forwards"}).addEventListener("finish",(()=>{r.remove()}))}else r.remove()}_ringIcon(t){const e=t.querySelector(".error-block-under-input__icon--animated");e&&(this._cancelAnimations(e),this._canAnimate(e)&&e.animate([{transform:"rotate(0deg) scale(1)",opacity:0},{transform:"rotate(-8deg) scale(1.05)",opacity:1,offset:.15},{transform:"rotate(8deg) scale(1.05)",opacity:1,offset:.3},{transform:"rotate(-6deg) scale(1.03)",opacity:1,offset:.45},{transform:"rotate(6deg) scale(1.03)",opacity:1,offset:.6},{transform:"rotate(0deg) scale(1)",opacity:0}],{duration:this._anim.iconDuration,easing:"ease"}))}destroy(){this._onSubmitHandler&&(this.$el.removeEventListener("submit",this._onSubmitHandler),this._onSubmitHandler=void 0),this._onSubmitClickHandler&&(this.$submits.forEach((t=>{t.removeEventListener("click",this._onSubmitClickHandler)})),this._onSubmitClickHandler=void 0);const t=this.$el.querySelector(".error-block-under-input__wrapper");t&&(this._cancelAnimations(t),t.remove()),this.$submits.forEach((t=>{t.classList.remove("button--loading");const e=t.querySelector(".button__loader");e&&e.remove()}));this.$el.querySelectorAll("input, textarea, select").forEach((t=>this.hideError(t))),document.documentElement.removeAttribute("data-now-scrolling"),this.inputs=null,this.$submits=[],this.waitResponse=!1}isCorrectArguments(){return"FORM"!==this.$el.tagName?(console.warn("$el не является тегом form"),!1):0!==this.$submits.length||(console.warn("В форме нет кнопки с типом submit"),!1)}formDataToObject(t){const e={};return t.forEach(((t,r)=>{Object.prototype.hasOwnProperty.call(e,r)?(Array.isArray(e[r])||(e[r]=[e[r]]),e[r].push(t)):e[r]=t})),e}appendFormDataValue(t,e,r){null!=r&&(Array.isArray(r)?r.forEach(((r,o)=>{this.appendFormDataValue(t,`${e}[${o}]`,r)})):r instanceof Blob?t.append(e,r):t.append(e,"object"==typeof r?JSON.stringify(r):String(r)))}objectToFormData(t){const e=new FormData;return Object.entries(t).forEach((([t,r])=>{this.appendFormDataValue(e,t,r)})),e}stringifyFormDataValue(t){return"string"==typeof t?t:t.name}formDataToUrlSearchParams(t){const e=new URLSearchParams;return t.forEach(((t,r)=>{e.append(r,this.stringifyFormDataValue(t))})),e}formDataToTextBody(t){return Array.from(t).map((([t,e])=>`${encodeURIComponent(t)}=${encodeURIComponent(this.stringifyFormDataValue(e))}`)).join("\n")}isBodylessMethod(t){return"GET"===t||"HEAD"===t}appendQueryString(t,e){const r=e.toString();if(!r)return t;const o=t.includes("?")?"&":"?";return`${t}${o}${r}`}findFieldByName(t){return Array.from(this.$el.querySelectorAll("input, textarea, select")).find((e=>e.name===t))||null}parseResponseBody(t){return e(this,void 0,void 0,(function*(){try{const e=yield t.json();return e&&"object"==typeof e?e:{success:!1,error:!0,"error-msg":"Некорректный ответ сервера"}}catch(t){return{success:!1,error:!0,"error-msg":"Некорректный ответ сервера"}}}))}handleUnsuccessfulResponse(t){if(!0===t.error&&(t["error-msg"]&&this.showErrorForm(t["error-msg"]),t.errors)){const e=[];t.errors.forEach((t=>{const r=this.findFieldByName(t.name);r?(e.push(r),this.showError(r,t["error-msg"])):console.warn(`Не найдено поле с именем: ${t.name}, для вывода ошибки: ${t["error-msg"]}`)})),!0===this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(e)}}sendData(){return e(this,void 0,void 0,(function*(){var t;const e=this.$el.getAttribute("action")||"",r=(null===(t=this.$el.getAttribute("method"))||void 0===t?void 0:t.toUpperCase())||"POST",o=this.$el.getAttribute("enctype")||"application/x-www-form-urlencoded";let n=null,i={};const s=a(this.$el);let l=this.formDataToObject(s);if(this.config.logging)for(const t of s.keys())console.log(s.get(t));const c="function"==typeof this.config.wrapData;"function"==typeof this.config.wrapData&&(l=this.config.wrapData(l));const d=c?this.objectToFormData(l):s;if(this.isBodylessMethod(r))return fetch(this.appendQueryString(e,this.formDataToUrlSearchParams(d)),{method:r,headers:i});switch(o){case"application/x-www-form-urlencoded":n=this.formDataToUrlSearchParams(d).toString(),i["Content-Type"]="application/x-www-form-urlencoded";break;case"multipart/form-data":n=d;break;case"text/plain":n=this.formDataToTextBody(d),i["Content-Type"]="text/plain";break;case"application/json":n=JSON.stringify(l),i["Content-Type"]="application/json";break;default:console.warn(`Неизвестный enctype: ${o}. Используется application/x-www-form-urlencoded.`),n=this.formDataToUrlSearchParams(d).toString(),i["Content-Type"]="application/x-www-form-urlencoded"}return fetch(e,{method:r,body:n,headers:i})}))}submit(){return e(this,void 0,void 0,(function*(){var t,e,r,o,n,i,s,a;if(!this.waitResponse){!0===this.config.showLoaderButton&&this.showLoader(),null===(e=(t=this.config).onSubmit)||void 0===e||e.call(t,this),this.waitResponse=!0;let c=!1;const d=()=>{c||(this.waitResponse=!1,!0===this.config.showLoaderButton&&this.hideLoader(),c=!0)};try{const t=yield this.sendData(),e=yield this.parseResponseBody(t);d(),null===(o=(r=this.config).onResponse)||void 0===o||o.call(r,e,this),t.ok&&!0===e.success?null===(i=(n=this.config).onResponseSuccess)||void 0===i||i.call(n,e,this):(null===(a=(s=this.config).onResponseUnsuccess)||void 0===a||a.call(s,e,this),this.handleUnsuccessfulResponse(e)),l(e)}catch(t){throw d(),t}}}))}}return t.blockScrollBody=function(){window.lastBlockScrollPosition=window.pageYOffset,document.body.style.top=-window.lastBlockScrollPosition+"px",document.body.classList.add("body--block-scroll")},t.closest=r,t.default=v,t.getAllValidators=function(){return new Map(u)},t.getValidator=p,t.isEmailValid=function(t){return/^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/iu.test(t)},t.isPhoneValid=function(t){const e=[];return Array.from(t).forEach((t=>{(Number(t)||0===Number(t))&&e.push(t)})),!(e.length<11||e.length<12&&"+"===t[1])},t.isUrlValid=s,t.parseCommonResponseProperties=l,t.registerValidator=function(t,e,r,{override:o=!1}={}){!u.has(t)||o?u.set(t,{fn:e,defaultMessage:r}):console.warn(`[FormFather] Validator "${t}" already exists; pass { override: true } to replace`)},t.serializeFormToJSON=function(t){const e={};return new FormData(t).forEach(((t,r)=>{e.hasOwnProperty(r)?(Array.isArray(e[r])||(e[r]=[e[r]]),e[r].push(t)):e[r]=t})),e},t.serializeToFormData=a,t.unblockScrollBody=function(){document.body.classList.remove("body--block-scroll"),document.body.style.top="",window.scrollTo(0,window.lastBlockScrollPosition||0)},Object.defineProperty(t,"__esModule",{value:!0}),t}({});
|
|
2
|
+
//# sourceMappingURL=FormFather.min.js.map
|