form-father 0.2.8 → 0.2.9
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 +204 -6
- package/RESPONSE_API.md +233 -0
- package/dist/FormFather.min.js +2 -0
- package/{index.js → dist/index.js} +59 -1
- package/{index.js.map → dist/index.js.map} +1 -1
- package/package.json +20 -3
- package/FormFather.min.js +0 -2
- /package/{FormFather.min.js.map → dist/FormFather.min.js.map} +0 -0
- /package/{index.d.ts → dist/index.d.ts} +0 -0
- /package/{types → dist/types}/helpers.d.ts +0 -0
- /package/{types → dist/types}/index.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
39
|
import Form 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,6 +52,7 @@ const options = {
|
|
|
32
52
|
},
|
|
33
53
|
onResponse: (responseBody, formInstance) => {
|
|
34
54
|
// Действия при получении ответа от сервера
|
|
55
|
+
formInstance.clearInputs();
|
|
35
56
|
},
|
|
36
57
|
// Другие опции...
|
|
37
58
|
};
|
|
@@ -39,6 +60,13 @@ const options = {
|
|
|
39
60
|
const form = new Form(formElement, options);
|
|
40
61
|
```
|
|
41
62
|
|
|
63
|
+
## Формат ответов сервера
|
|
64
|
+
|
|
65
|
+
Данная библиотека абстрагирует процесс парсинга HTTP-ответов и выполняет действия на их основе. Для её корректной работы
|
|
66
|
+
сервер должен возвращать ответы в строго определённом формате. Полное описание формата (схемы) ответа от сервера
|
|
67
|
+
представлено в данном техническом документе:
|
|
68
|
+
[Спецификация формата ответов API.](https://github.com/Poliklot/form-father/blob/master/RESPONSE_API.md)
|
|
69
|
+
|
|
42
70
|
## Опции
|
|
43
71
|
|
|
44
72
|
- **onSubmit**: Функция обратного вызова, вызываемая при отправке формы.
|
|
@@ -50,11 +78,13 @@ const form = new Form(formElement, options);
|
|
|
50
78
|
- **customTypeError**: Кастомный тип ошибки.
|
|
51
79
|
- **loaderColor**: Цвет лоадера в кнопке отправки.
|
|
52
80
|
- **logging**: Нужно ли выводить данные в консоль. По умолчанию `false`.
|
|
53
|
-
- **setDefaultParams(params)**: Метод setDefaultParams используется для установки значений по умолчанию для всех экземпляров формы. Эти параметры можно переопределить при инициализации конкретной формы.
|
|
54
81
|
|
|
55
82
|
## Методы
|
|
56
83
|
|
|
57
84
|
- **clearInputs()**: Очищает все поля ввода формы.
|
|
85
|
+
- **setDefaultParams(params)**: Метод setDefaultParams используется для установки значений по умолчанию для всех
|
|
86
|
+
экземпляров формы. Эти параметры можно переопределить при инициализации конкретной формы.
|
|
87
|
+
- **destroy()**: Очищает инициализацию формы.
|
|
58
88
|
|
|
59
89
|
## Хелперы
|
|
60
90
|
|
|
@@ -62,11 +92,179 @@ const form = new Form(formElement, options);
|
|
|
62
92
|
|
|
63
93
|
- **serializeToFormData($element)**: Сериализует данные формы в объект `FormData`.
|
|
64
94
|
- **isEmailValid(value)**: Проверяет, является ли строка валидным адресом электронной почты.
|
|
65
|
-
- **isUrlValid(value)**:
|
|
95
|
+
- **isUrlValid(value)**: Проверяет `http(s)`-адрес, домен, IP, `localhost`, схема может быть опущена.
|
|
66
96
|
- **isPhoneValid(value)**: Проверяет, является ли строка валидным номером телефона.
|
|
67
97
|
- **closest($el, selector)**: Находит ближайший родительский элемент по заданному селектору.
|
|
68
98
|
- **blockScrollBody()**: Блокирует прокрутку страницы.
|
|
69
99
|
- **unblockScrollBody()**: Разблокирует прокрутку страницы.
|
|
70
100
|
- **parseCommonResponseProperties(responseBody)**: Обрабатывает общие свойства ответа сервера.
|
|
71
101
|
|
|
72
|
-
|
|
102
|
+
## Валидаторы
|
|
103
|
+
|
|
104
|
+
Form Father поставляется с базовым набором правил валидации, а также позволяет легко добавлять и переопределять
|
|
105
|
+
собственные.
|
|
106
|
+
|
|
107
|
+
### Базовые правила
|
|
108
|
+
|
|
109
|
+
| Ключ | Проверка | Сообщение по умолчанию |
|
|
110
|
+
| ------------- | ------------------------------- | ---------------------- |
|
|
111
|
+
| `required` | значение непустое | **Пустое значение** |
|
|
112
|
+
| `email` | валидный e-mail | **Неверный формат** |
|
|
113
|
+
| `tel` | российский номер `+7XXXXXXXXXX` | **Неверный формат** |
|
|
114
|
+
| `url` | домен/IP/localhost, схема optional | **Неверный формат** |
|
|
115
|
+
| `not-numbers` | строка без цифр | **Неверный формат** |
|
|
116
|
+
|
|
117
|
+
> [!NOTE] Полный список валидаторов можно получить так:
|
|
118
|
+
>
|
|
119
|
+
> ```js
|
|
120
|
+
> import { getAllValidators } from 'form-father';
|
|
121
|
+
>
|
|
122
|
+
> const all = getAllValidators();
|
|
123
|
+
> console.log([...all.keys()]);
|
|
124
|
+
> ```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
### Глобальная схема (`Form.defaultValidationSchema`)
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
Form.defaultValidationSchema = {
|
|
132
|
+
email: {
|
|
133
|
+
selector: 'input[type="email"]', // CSS-селектор, по которому выбираются inputs
|
|
134
|
+
rules: ['required', 'email'],
|
|
135
|
+
},
|
|
136
|
+
tel: {
|
|
137
|
+
selector: 'input[type="tel"]',
|
|
138
|
+
rules: ['required', 'tel'],
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
- **selector** — произвольный CSS. Если он указан, правила применяются ко всем элементам, соответствующим этому
|
|
144
|
+
селектору. Если селектора нет, библиотека использует атрибут `data-validate`.
|
|
145
|
+
- Ту же схему можно переопределить/дополнить локально через `validationSchema` при инициализации формы.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### Локальная схема (`validationSchema`)
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
const form = new Form($form, {
|
|
153
|
+
validationSchema: {
|
|
154
|
+
tel: {
|
|
155
|
+
// ключ произвольный
|
|
156
|
+
selector: '[data-validate="tel"]',
|
|
157
|
+
rules: ['tel', 'begin+79277'],
|
|
158
|
+
messages: {
|
|
159
|
+
tel: 'Телефон в формате +7…',
|
|
160
|
+
'begin+79277': 'Номер должен начинаться на +79277',
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
_Порядок применения для одного `<input>`:_
|
|
168
|
+
|
|
169
|
+
1. `required` (если атрибут `required`)
|
|
170
|
+
2. правила из `validationSchema`
|
|
171
|
+
3. правила из `data-custom-validate`
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
### Правило через `data-custom-validate`
|
|
176
|
+
|
|
177
|
+
```html
|
|
178
|
+
<input name="code" data-custom-validate="only-digits:6" required />
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
- Если правило неизвестно, в консоль выводится `[FormFather] Unknown validation rule "foo"`.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
### Собственный валидатор
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
import { registerValidator } from 'form-father';
|
|
189
|
+
|
|
190
|
+
registerValidator(
|
|
191
|
+
'only-digits',
|
|
192
|
+
(v, _input, _form, len) => new RegExp(`^\d{${len}}$`).test(v),
|
|
193
|
+
'Допустимы только цифры',
|
|
194
|
+
);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Теперь можно использовать:
|
|
198
|
+
|
|
199
|
+
```html
|
|
200
|
+
<input name="code" data-custom-validate="only-digits:6" required />
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
### Переопределение валидатора
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
registerValidator(
|
|
209
|
+
'tel',
|
|
210
|
+
v => /^8\d{10}$/.test(v), // новая проверка
|
|
211
|
+
'Номер должен начинаться на 8…', // новое сообщение
|
|
212
|
+
{ override: true }, // ← обязательно!
|
|
213
|
+
);
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
_Без `override: true` библиотека оставит старую версию и выведет warn._
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
### Изменение только текста ошибки
|
|
221
|
+
|
|
222
|
+
#### Глобально
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
const vd = getValidator('email');
|
|
226
|
+
if (vd) vd.defaultMessage = 'Некорректный e-mail';
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
#### Локально для формы
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
validationSchema: {
|
|
233
|
+
email: {
|
|
234
|
+
selector: 'input[type="email"]',
|
|
235
|
+
rules: ['email'],
|
|
236
|
+
messages: { email: 'Некорректный e-mail' },
|
|
237
|
+
},
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
### Итоговый порядок проверки
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
required → schema.rules → data-custom-validate
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
- Для каждого поля отображается **только первое** найденное сообщение об ошибке.
|
|
250
|
+
- Неизвестные правила логируются и пропускаются до начала проверки, поэтому не влияют на результат валидации.
|
|
251
|
+
|
|
252
|
+
## Сборка и разработка
|
|
253
|
+
|
|
254
|
+
Для сборки проекта используется **Rollup**. Основные команды:
|
|
255
|
+
|
|
256
|
+
- **npm run build**: Сборка проекта.
|
|
257
|
+
- **npm run watch**: Сборка в режиме наблюдения.
|
|
258
|
+
- **npm run demos**: Запуск демо-версии с использованием Vite.
|
|
259
|
+
|
|
260
|
+
## Вклад
|
|
261
|
+
|
|
262
|
+
Будем рады вашим предложениям и улучшениям! Пожалуйста, создавайте
|
|
263
|
+
[issues](https://github.com/poliklot/form-father/issues) и отправляйте
|
|
264
|
+
[pull requests](https://github.com/poliklot/form-father/pulls).
|
|
265
|
+
|
|
266
|
+
## Лицензия
|
|
267
|
+
|
|
268
|
+
[MIT](LICENSE)
|
|
269
|
+
|
|
270
|
+
© 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,i,r){return new(i||(i=Promise))((function(n,o){function s(t){try{l(r.next(t))}catch(t){o(t)}}function a(t){try{l(r.throw(t))}catch(t){o(t)}}function l(t){var e;t.done?n(t.value):(e=t.value,e instanceof i?e:new i((function(t){t(e)}))).then(s,a)}l((r=r.apply(t,e||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;const i=["http:","https:"];function r(t){return t.length>0&&t.length<=63&&/^[a-z\d-]+$/i.test(t)&&!t.startsWith("-")&&!t.endsWith("-")}function n(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(r)}(t))}function o(t){const e="FORM"===t.tagName?Array.from(t.elements):Array.from(t.querySelectorAll("input, select, textarea, button")),i=new FormData;return e.forEach((t=>{if(!(t instanceof HTMLInputElement||t instanceof HTMLSelectElement||t instanceof HTMLTextAreaElement||t instanceof HTMLButtonElement))return;if(!t.name||t.hasAttribute("data-no-serialize"))return;const{name:e,type:r}=t;if("file"===r)if("true"===t.getAttribute("multiple")){const r=t.files;if(r)for(let t=0;t<r.length;t++)i.append(`${e}[${t}]`,r[t])}else{const r=t.files;r&&r[0]&&i.append(e,r[0])}else{let n;if("select-one"===r||"select-multiple"===r){const o=t;if("select-multiple"===r){Array.from(o.selectedOptions).map((t=>t.value)).forEach(((t,r)=>{i.append(`${e}[${r}]`,t)}))}else n=o.value,n&&i.append(e,n)}else if("tel"===r)n=t.value.split(/[\s()-]/).join("").replace(/^8/,"+7");else if("checkbox"===r||"radio"===r){if(!t.checked)return;n=t.value}else if("date"===r){const e=t.getAttribute("data-date-format");n=e?function(t,e){const i=new Date(t),r=t=>t.toString().padStart(2,"0"),n=r(i.getDate()),o=r(i.getMonth()+1),s=i.getFullYear().toString();return e.replace("dd",n).replace("mm",o).replace("yyyy",s)}(t.value,e):t.value}else n=t.value;n&&"select-one"!==r&&"select-multiple"!==r&&i.append(e,n)}})),i}var s;const a=Symbol.for("FormFather.validators"),l=null!==(s=globalThis[a])&&void 0!==s?s:globalThis[a]=new Map;function c(t,e,i,{override:r=!1}={}){!l.has(t)||r?l.set(t,{fn:e,defaultMessage:i}):console.warn(`[FormFather] Validator "${t}" already exists; pass { override: true } to replace`)}const d=t=>l.get(t);var u;c("required",((t,e,i)=>{if(e&&e.type){if("checkbox"===e.type)return e.checked;if("radio"===e.type){const t=i.querySelectorAll(`input[type="radio"][name="${e.name}"]`);return Array.from(t).some((t=>t.checked))}}return t.trim().length>0}),"Пустое значение"),c("email",(t=>/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)),"Неверный формат"),c("tel",(t=>/^\+7\d{10}$/.test(t)),"Неверный формат"),c("url",(t=>function(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 i.includes(t.protocol)&&n(t.hostname)}catch(t){return!1}}(t)),"Неверный формат"),c("not-numbers",(t=>!/[0-9]/.test(t)),"Неверный формат");const p=Symbol.for("formfather.shared"),h=null!==(u=globalThis[p])&&void 0!==u?u:globalThis[p]={defaultParams:{},defaultValidationSchema:void 0};let f=0;const m={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 g{static get defaultParams(){return h.defaultParams}static set defaultParams(t){h.defaultParams=t}static get defaultValidationSchema(){var t;return null!==(t=h.defaultValidationSchema)&&void 0!==t?t:h.defaultValidationSchema=m}static set defaultValidationSchema(t){h.defaultValidationSchema=t}constructor(t,i){this.$submits=[],this.waitResponse=!1,this.inputs=null,this._anim={duration:220,easing:"ease",paddingTopOpened:16,iconDuration:700},this.$el=t,this.options=i;this.config=Object.assign({},{onSubmit:()=>{},onResponse:()=>{},onResponseSuccess:()=>{},onResponseUnsuccess:()=>{},customTypeError:null,loaderColor:"#fff",showLoaderButton:!0,scrollToFirstErroredInput:!0,logging:!1,inputSelector:".input",inputWrapperSelector:".input-primary"},g.defaultParams,i),this.$el?(this.$submits=this.findSubmitElements(),this._onSubmitClickHandler=t=>e(this,void 0,void 0,(function*(){var e,i,r,n;t.preventDefault(),null===(i=(e=this.config).onBeforeValidate)||void 0===i||i.call(e,this);const o=yield this.validate();null===(n=(r=this.config).onAfterValidate)||void 0===n||n.call(r,o,this),o&&(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"], button[type="submit"]'))}initialization(){this.inputs=this.$el.querySelectorAll(this.config.inputSelector),this._onSubmitHandler=t=>e(this,void 0,void 0,(function*(){var e,i,r,n;t.preventDefault(),null===(i=(e=this.config).onBeforeValidate)||void 0===i||i.call(e,this);const o=yield this.validate();null===(n=(r=this.config).onAfterValidate)||void 0===n||n.call(r,o,this),o&&this.submit()})),this.$el.addEventListener("submit",this._onSubmitHandler)}showError(t,e){(function(t,e){if(null==t)return null;let i=t;for(;i;){if(i.matches(e))return i;i=i.parentElement}return null})(t,this.config.inputWrapperSelector).showError(e)}showLoader(){this.$submits.forEach((t=>{if(t.classList.add("button--loading"),t.querySelector(".button__loader"))return;const e="form-father-loader-"+f++,i=`\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`,r=document.createElement("span");r.innerHTML=i,r.className="button__loader",t.insertAdjacentElement("afterbegin",r),requestAnimationFrame((()=>{r.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){const e=t.parentElement;e&&e.classList.contains("input__wrapper")&&(e.getAttribute("data-type-error"),e.classList.remove("input__wrapper--error"))}scrollToFirstErroredInput(t){if(t.length>0){const e=Array.from(this.$el.querySelectorAll("input"));let i=null,r=1/0;if(t.forEach((t=>{const n=e.findIndex((e=>e===t));n<r&&(r=n,i=t)})),i){const t=i.getBoundingClientRect(),e=window.pageYOffset||document.documentElement.scrollTop,r=t.top+e-window.innerHeight/2;document.documentElement.setAttribute("data-now-scrolling",""),window.scrollTo({top:r,behavior:"smooth"}),setTimeout((()=>{document.documentElement.removeAttribute("data-now-scrolling")}),800)}}}validate(){return e(this,arguments,void 0,(function*(t=this.$el){const i=Object.assign(Object.assign({},g.defaultValidationSchema||{}),this.config.validationSchema||{}),r=t=>console.warn(`[FormFather] Unknown validation rule "${t}"`),n=[],o=new WeakSet;let s=!0;const a=(t,e=[])=>{var i;const n=[];t.hasAttribute("required")&&n.push("required"),n.push(...e);const o=(null!==(i=t.getAttribute("data-custom-validate"))&&void 0!==i?i:"").split(",").map((t=>t.trim())).filter(Boolean);n.push(...o);const s=[];for(const t of n){const e="string"==typeof t?t:t.rule;d(e)?s.push(t):r(e)}return s},l=(i,r,...o)=>e(this,[i,r,...o],void 0,(function*(e,i,r={}){if(e.hasAttribute("data-no-validate"))return;if(!("checkbox"===e.type||"radio"===e.type?!e.checked:0===e.value.trim().length)||i.includes("required")){for(const o of i){const{rule:i,params:a}="string"==typeof o?{rule:o,params:void 0}:o,l=d(i),c=yield l.fn(e.value,e,t,a),u="object"==typeof c&&null!==c,p=u?!0===c.valid:!!c;if(u&&c.effect&&(yield c.effect({value:e.value,$input:e,$form:t,params:a})),!p){s=!1;const t=c,o=u&&"string"==typeof t.message&&t.message||r[i]||l.defaultMessage;return this.showError(e,o),void n.push(e)}if(u&&c.stopOthers)return}this.hideError(e)}else this.hideError(e)}));for(const[e,r]of Object.entries(i)){const{selector:i,rules:n=[],messages:s={}}=r,c=i?t.querySelectorAll(i):t.querySelectorAll(`[data-validate="${e}"]`);for(const t of Array.from(c))o.has(t)||(o.add(t),yield l(t,a(t,n),s))}const c=Array.from(t.querySelectorAll("[data-custom-validate]")).filter((t=>!o.has(t)));for(const t of c)o.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 i=t;"radio"===i.type||"checkbox"===i.type?i.checked=!1:(i.value="",null===(e=i.parentElement)||void 0===e||e.classList.remove("filled"),i.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;null===(e=null==t?void 0:t.getAnimations)||void 0===e||e.call(t).forEach((t=>t.cancel()))}showErrorForm(t){var e;const i=this.$el.querySelectorAll(this.config.inputWrapperSelector),r=i[i.length-1];let n=this.$el.querySelector(".error-block-under-input__wrapper");n&&r.nextElementSibling===n?(n.style.boxSizing="border-box",n.style.overflow="hidden"):(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"}),r.insertAdjacentElement("afterend",n)),n.querySelector(".error-block-under-input__main-text").textContent=t;const o=n.querySelector(".error-block-under-input__secondary-text");o&&(o.textContent=""),null===(e=n.querySelector(".error-block-under-input"))||void 0===e||e.classList.remove("error-block-under-input--success"),n.querySelector(".error-block-under-input");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].nextElementSibling;if(!e||!e.classList.contains("error-block-under-input__wrapper"))return;if((""===e.style.height||"auto"===e.style.height)&&(e.style.height=`${e.clientHeight}px`),e.style.paddingTop=e.style.paddingTop||`${this._anim.paddingTopOpened}px`,e.style.opacity=e.style.opacity||"1",this._cancelAnimations(e),this._canAnimate(e)){e.animate([{height:`${parseFloat(e.style.height)||e.clientHeight}px`,paddingTop:`${parseFloat(e.style.paddingTop)||this._anim.paddingTopOpened}px`,opacity:parseFloat(e.style.opacity||"1")},{height:"0px",paddingTop:"0px",opacity:0}],{duration:this._anim.duration,easing:this._anim.easing,fill:"forwards"}).addEventListener("finish",(()=>{e.remove()}))}else e.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").forEach((t=>{t instanceof HTMLInputElement&&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)}sendData(){return e(this,void 0,void 0,(function*(){var t;const e=this.$el.getAttribute("action")||"",i=(null===(t=this.$el.getAttribute("method"))||void 0===t?void 0:t.toUpperCase())||"POST",r=this.$el.getAttribute("enctype")||"application/x-www-form-urlencoded";let n=null,s={};const a=o(this.$el);let l={};if(a.forEach(((t,e)=>{l[e]=t})),this.config.logging)for(const t of a.keys())console.log(a.get(t));switch("function"==typeof this.config.wrapData&&(l=this.config.wrapData(l)),r){case"application/x-www-form-urlencoded":n=new URLSearchParams(o(this.$el)).toString(),s["Content-Type"]="application/x-www-form-urlencoded";break;case"multipart/form-data":n=o(this.$el);break;case"text/plain":n=Array.from(o(this.$el)).map((([t,e])=>`${encodeURIComponent(t)}=${encodeURIComponent(e)}`)).join("\n"),s["Content-Type"]="text/plain";break;case"application/json":n=JSON.stringify(l),s["Content-Type"]="application/json";break;default:console.warn(`Неизвестный enctype: ${r}. Используется application/x-www-form-urlencoded.`),n=new URLSearchParams(o(this.$el)).toString(),s["Content-Type"]="application/x-www-form-urlencoded"}return fetch(e,{method:i,body:n,headers:s})}))}submit(){return e(this,void 0,void 0,(function*(){var t,e,i,r,n,o,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;const l=yield this.sendData(),c=yield l.json();if(this.waitResponse=!1,!0===this.config.showLoaderButton&&this.hideLoader(),null===(r=(i=this.config).onResponse)||void 0===r||r.call(i,c,this),200===l.status){if(!0===c.success)null===(o=(n=this.config).onResponseSuccess)||void 0===o||o.call(n,c,this);else if(null===(a=(s=this.config).onResponseUnsuccess)||void 0===a||a.call(s,c,this),!0===c.error&&(c["error-msg"]&&this.showErrorForm(c["error-msg"]),c.errors)){const t=[];c.errors.forEach((e=>{const i=this.$el.querySelector(`input[name="${e.name}"]`);i?(t.push(i),this.showError(i,e["error-msg"])):console.warn(`Не найдено поле с именем: ${e.name}, для вывода ошибки: ${e["error-msg"]}`)})),!0===this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(t)}!function(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"])}(c)}}}))}}return t.default=g,t.getAllValidators=function(){return new Map(l)},t.getValidator=d,t.registerValidator=c,Object.defineProperty(t,"__esModule",{value:!0}),t}({});
|
|
2
|
+
//# sourceMappingURL=FormFather.min.js.map
|
|
@@ -73,6 +73,64 @@ function closest($el, selector) {
|
|
|
73
73
|
}
|
|
74
74
|
return null;
|
|
75
75
|
}
|
|
76
|
+
const HTTP_PROTOCOLS = ['http:', 'https:'];
|
|
77
|
+
function getNormalizedUrlString(originalUrlString) {
|
|
78
|
+
const trimmedUrl = originalUrlString.trim();
|
|
79
|
+
if (trimmedUrl.length === 0)
|
|
80
|
+
return '';
|
|
81
|
+
if (trimmedUrl.startsWith('//')) {
|
|
82
|
+
return `https:${trimmedUrl}`;
|
|
83
|
+
}
|
|
84
|
+
return /^[a-z][a-z\d+.-]*:\/\//i.test(trimmedUrl) ? trimmedUrl : `https://${trimmedUrl}`;
|
|
85
|
+
}
|
|
86
|
+
function isValidIpv4(hostname) {
|
|
87
|
+
const parts = hostname.split('.');
|
|
88
|
+
if (parts.length !== 4)
|
|
89
|
+
return false;
|
|
90
|
+
return parts.every(part => {
|
|
91
|
+
if (!/^\d{1,3}$/.test(part))
|
|
92
|
+
return false;
|
|
93
|
+
const num = Number(part);
|
|
94
|
+
return num >= 0 && num <= 255;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function isValidIpv6(hostname) {
|
|
98
|
+
if (!hostname.startsWith('[') || !hostname.endsWith(']'))
|
|
99
|
+
return false;
|
|
100
|
+
const normalizedHostname = hostname.slice(1, -1);
|
|
101
|
+
return normalizedHostname.includes(':') && /^[\da-f:.]+$/i.test(normalizedHostname);
|
|
102
|
+
}
|
|
103
|
+
function isValidDomainLabel(label) {
|
|
104
|
+
return label.length > 0 && label.length <= 63 && /^[a-z\d-]+$/i.test(label) && !label.startsWith('-') && !label.endsWith('-');
|
|
105
|
+
}
|
|
106
|
+
function isValidDomainHostname(hostname) {
|
|
107
|
+
const labels = hostname.split('.');
|
|
108
|
+
return labels.length >= 2 && labels.every(isValidDomainLabel);
|
|
109
|
+
}
|
|
110
|
+
function isAllowedHostname(hostname) {
|
|
111
|
+
if (!hostname)
|
|
112
|
+
return false;
|
|
113
|
+
return (hostname === 'localhost' || isValidIpv4(hostname) || isValidIpv6(hostname) || isValidDomainHostname(hostname));
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Проверяет является ли строка валидным url.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} originalUrlString - Строка, которую нужно проверить.
|
|
119
|
+
*
|
|
120
|
+
* @returns {boolean} Является ли строка валидным url адресом true/false.
|
|
121
|
+
*/
|
|
122
|
+
function isUrlValid(originalUrlString) {
|
|
123
|
+
const urlString = getNormalizedUrlString(originalUrlString);
|
|
124
|
+
if (urlString.length === 0)
|
|
125
|
+
return false;
|
|
126
|
+
try {
|
|
127
|
+
const url = new URL(urlString);
|
|
128
|
+
return HTTP_PROTOCOLS.includes(url.protocol) && isAllowedHostname(url.hostname);
|
|
129
|
+
}
|
|
130
|
+
catch (_a) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
76
134
|
/**
|
|
77
135
|
* Кастомная сериализация формы в объект FormData.
|
|
78
136
|
*
|
|
@@ -232,7 +290,7 @@ registerValidator('required', (value, $input, $form) => {
|
|
|
232
290
|
}, 'Пустое значение');
|
|
233
291
|
registerValidator('email', v => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v), 'Неверный формат');
|
|
234
292
|
registerValidator('tel', v => /^\+7\d{10}$/.test(v), 'Неверный формат');
|
|
235
|
-
registerValidator('url', v =>
|
|
293
|
+
registerValidator('url', v => isUrlValid(v), 'Неверный формат');
|
|
236
294
|
registerValidator('not-numbers', v => !/[0-9]/.test(v), 'Неверный формат');
|
|
237
295
|
|
|
238
296
|
var _a;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "form-father",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Form Father: Библиотека для обработки форм",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"files": ["dist", "README.md", "LICENSE", "RESPONSE_API.md"],
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
6
13
|
"scripts": {
|
|
7
14
|
"build": "rollup -c",
|
|
8
15
|
"watch": "rollup -c -w",
|
|
9
16
|
"release": "node scripts/release.js",
|
|
17
|
+
"test": "jest --coverage",
|
|
18
|
+
"prepublishOnly": "npm test -- --runInBand --watchman=false && npm run build",
|
|
19
|
+
"versions:sync": "node scripts/sync-version.js",
|
|
10
20
|
"demos": "BUILD_TARGET=demos npm run build && concurrently --kill-others \"vite\" \"npm run watch\""
|
|
11
21
|
},
|
|
12
22
|
"repository": {
|
|
13
23
|
"type": "git",
|
|
14
|
-
"url": "https://github.com/poliklot/form-father.git"
|
|
24
|
+
"url": "git+https://github.com/poliklot/form-father.git"
|
|
15
25
|
},
|
|
16
26
|
"keywords": [
|
|
17
27
|
"form",
|
|
@@ -34,10 +44,15 @@
|
|
|
34
44
|
"@rollup/plugin-json": "^6.1.0",
|
|
35
45
|
"@rollup/plugin-node-resolve": "^15.3.0",
|
|
36
46
|
"@rollup/plugin-terser": "^0.4.4",
|
|
47
|
+
"@types/jest": "^30.0.0",
|
|
48
|
+
"@types/jsdom": "^21.1.7",
|
|
37
49
|
"chalk": "^5.3.0",
|
|
38
50
|
"concurrently": "^9.0.1",
|
|
39
51
|
"fs-extra": "^11.2.0",
|
|
40
52
|
"inquirer": "^12.1.0",
|
|
53
|
+
"jest": "^30.0.5",
|
|
54
|
+
"jest-environment-jsdom": "^30.0.5",
|
|
55
|
+
"jsdom": "^26.1.0",
|
|
41
56
|
"ora": "^8.1.1",
|
|
42
57
|
"postcss-preset-env": "^10.0.8",
|
|
43
58
|
"prettier-plugin-jsdoc": "^1.3.0",
|
|
@@ -47,7 +62,9 @@
|
|
|
47
62
|
"rollup-plugin-dts": "^6.1.1",
|
|
48
63
|
"rollup-plugin-typescript2": "^0.36.0",
|
|
49
64
|
"sass-embedded": "^1.80.5",
|
|
65
|
+
"ts-jest": "^29.4.0",
|
|
66
|
+
"ts-node": "^10.9.2",
|
|
50
67
|
"typescript": "^5.6.3",
|
|
51
68
|
"vite": "^5.4.10"
|
|
52
69
|
}
|
|
53
|
-
}
|
|
70
|
+
}
|
package/FormFather.min.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
var FormFather=function(t){"use strict";function e(t,e,i,o){return new(i||(i=Promise))((function(r,n){function s(t){try{l(o.next(t))}catch(t){n(t)}}function a(t){try{l(o.throw(t))}catch(t){n(t)}}function l(t){var e;t.done?r(t.value):(e=t.value,e instanceof i?e:new i((function(t){t(e)}))).then(s,a)}l((o=o.apply(t,e||[])).next())}))}function i(t){const e="FORM"===t.tagName?Array.from(t.elements):Array.from(t.querySelectorAll("input, select, textarea, button")),i=new FormData;return e.forEach((t=>{if(!(t instanceof HTMLInputElement||t instanceof HTMLSelectElement||t instanceof HTMLTextAreaElement||t instanceof HTMLButtonElement))return;if(!t.name||t.hasAttribute("data-no-serialize"))return;const{name:e,type:o}=t;if("file"===o)if("true"===t.getAttribute("multiple")){const o=t.files;if(o)for(let t=0;t<o.length;t++)i.append(`${e}[${t}]`,o[t])}else{const o=t.files;o&&o[0]&&i.append(e,o[0])}else{let r;if("select-one"===o||"select-multiple"===o){const n=t;if("select-multiple"===o){Array.from(n.selectedOptions).map((t=>t.value)).forEach(((t,o)=>{i.append(`${e}[${o}]`,t)}))}else r=n.value,r&&i.append(e,r)}else if("tel"===o)r=t.value.split(/[\s()-]/).join("").replace(/^8/,"+7");else if("checkbox"===o||"radio"===o){if(!t.checked)return;r=t.value}else if("date"===o){const e=t.getAttribute("data-date-format");r=e?function(t,e){const i=new Date(t),o=t=>t.toString().padStart(2,"0"),r=o(i.getDate()),n=o(i.getMonth()+1),s=i.getFullYear().toString();return e.replace("dd",r).replace("mm",n).replace("yyyy",s)}(t.value,e):t.value}else r=t.value;r&&"select-one"!==o&&"select-multiple"!==o&&i.append(e,r)}})),i}var o;"function"==typeof SuppressedError&&SuppressedError;const r=Symbol.for("FormFather.validators"),n=null!==(o=globalThis[r])&&void 0!==o?o:globalThis[r]=new Map;function s(t,e,i,{override:o=!1}={}){!n.has(t)||o?n.set(t,{fn:e,defaultMessage:i}):console.warn(`[FormFather] Validator "${t}" already exists; pass { override: true } to replace`)}const a=t=>n.get(t);var l;s("required",((t,e,i)=>{if(e&&e.type){if("checkbox"===e.type)return e.checked;if("radio"===e.type){const t=i.querySelectorAll(`input[type="radio"][name="${e.name}"]`);return Array.from(t).some((t=>t.checked))}}return t.trim().length>0}),"Пустое значение"),s("email",(t=>/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)),"Неверный формат"),s("tel",(t=>/^\+7\d{10}$/.test(t)),"Неверный формат"),s("url",(t=>/^(https?:\/\/).+\..+/.test(t)),"Неверный формат"),s("not-numbers",(t=>!/[0-9]/.test(t)),"Неверный формат");const c=Symbol.for("formfather.shared"),d=null!==(l=globalThis[c])&&void 0!==l?l:globalThis[c]={defaultParams:{},defaultValidationSchema:void 0};let u=0;const p={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 h{static get defaultParams(){return d.defaultParams}static set defaultParams(t){d.defaultParams=t}static get defaultValidationSchema(){var t;return null!==(t=d.defaultValidationSchema)&&void 0!==t?t:d.defaultValidationSchema=p}static set defaultValidationSchema(t){d.defaultValidationSchema=t}constructor(t,i){this.$submits=[],this.waitResponse=!1,this.inputs=null,this._anim={duration:220,easing:"ease",paddingTopOpened:16,iconDuration:700},this.$el=t,this.options=i;this.config=Object.assign({},{onSubmit:()=>{},onResponse:()=>{},onResponseSuccess:()=>{},onResponseUnsuccess:()=>{},customTypeError:null,loaderColor:"#fff",showLoaderButton:!0,scrollToFirstErroredInput:!0,logging:!1,inputSelector:".input",inputWrapperSelector:".input-primary"},h.defaultParams,i),this.$el?(this.$submits=this.findSubmitElements(),this._onSubmitClickHandler=t=>e(this,void 0,void 0,(function*(){var e,i,o,r;t.preventDefault(),null===(i=(e=this.config).onBeforeValidate)||void 0===i||i.call(e,this);const n=yield this.validate();null===(r=(o=this.config).onAfterValidate)||void 0===r||r.call(o,n,this),n&&(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"], button[type="submit"]'))}initialization(){this.inputs=this.$el.querySelectorAll(this.config.inputSelector),this._onSubmitHandler=t=>e(this,void 0,void 0,(function*(){var e,i,o,r;t.preventDefault(),null===(i=(e=this.config).onBeforeValidate)||void 0===i||i.call(e,this);const n=yield this.validate();null===(r=(o=this.config).onAfterValidate)||void 0===r||r.call(o,n,this),n&&this.submit()})),this.$el.addEventListener("submit",this._onSubmitHandler)}showError(t,e){(function(t,e){if(null==t)return null;let i=t;for(;i;){if(i.matches(e))return i;i=i.parentElement}return null})(t,this.config.inputWrapperSelector).showError(e)}showLoader(){this.$submits.forEach((t=>{if(t.classList.add("button--loading"),t.querySelector(".button__loader"))return;const e="form-father-loader-"+u++,i=`\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=i,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){const e=t.parentElement;e&&e.classList.contains("input__wrapper")&&(e.getAttribute("data-type-error"),e.classList.remove("input__wrapper--error"))}scrollToFirstErroredInput(t){if(t.length>0){const e=Array.from(this.$el.querySelectorAll("input"));let i=null,o=1/0;if(t.forEach((t=>{const r=e.findIndex((e=>e===t));r<o&&(o=r,i=t)})),i){const t=i.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 i=Object.assign(Object.assign({},h.defaultValidationSchema||{}),this.config.validationSchema||{}),o=t=>console.warn(`[FormFather] Unknown validation rule "${t}"`),r=[],n=new WeakSet;let s=!0;const l=(t,e=[])=>{var i;const r=[];t.hasAttribute("required")&&r.push("required"),r.push(...e);const n=(null!==(i=t.getAttribute("data-custom-validate"))&&void 0!==i?i:"").split(",").map((t=>t.trim())).filter(Boolean);r.push(...n);const s=[];for(const t of r){const e="string"==typeof t?t:t.rule;a(e)?s.push(t):o(e)}return s},c=(i,o,...n)=>e(this,[i,o,...n],void 0,(function*(e,i,o={}){if(e.hasAttribute("data-no-validate"))return;if(!("checkbox"===e.type||"radio"===e.type?!e.checked:0===e.value.trim().length)||i.includes("required")){for(const n of i){const{rule:i,params:l}="string"==typeof n?{rule:n,params:void 0}:n,c=a(i),d=yield c.fn(e.value,e,t,l),u="object"==typeof d&&null!==d,p=u?!0===d.valid:!!d;if(u&&d.effect&&(yield d.effect({value:e.value,$input:e,$form:t,params:l})),!p){s=!1;const t=d,n=u&&"string"==typeof t.message&&t.message||o[i]||c.defaultMessage;return this.showError(e,n),void r.push(e)}if(u&&d.stopOthers)return}this.hideError(e)}else this.hideError(e)}));for(const[e,o]of Object.entries(i)){const{selector:i,rules:r=[],messages:s={}}=o,a=i?t.querySelectorAll(i):t.querySelectorAll(`[data-validate="${e}"]`);for(const t of Array.from(a))n.has(t)||(n.add(t),yield c(t,l(t,r),s))}const d=Array.from(t.querySelectorAll("[data-custom-validate]")).filter((t=>!n.has(t)));for(const t of d)n.add(t),yield c(t,l(t,[]));return!s&&this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(r.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 i=t;"radio"===i.type||"checkbox"===i.type?i.checked=!1:(i.value="",null===(e=i.parentElement)||void 0===e||e.classList.remove("filled"),i.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;null===(e=null==t?void 0:t.getAnimations)||void 0===e||e.call(t).forEach((t=>t.cancel()))}showErrorForm(t){var e;const i=this.$el.querySelectorAll(this.config.inputWrapperSelector),o=i[i.length-1];let r=this.$el.querySelector(".error-block-under-input__wrapper");r&&o.nextElementSibling===r?(r.style.boxSizing="border-box",r.style.overflow="hidden"):(r=document.createElement("div"),r.className="error-block-under-input__wrapper",r.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(r.style,{boxSizing:"border-box",overflow:"hidden",height:"0px",paddingTop:"0px",opacity:"0"}),o.insertAdjacentElement("afterend",r)),r.querySelector(".error-block-under-input__main-text").textContent=t;const n=r.querySelector(".error-block-under-input__secondary-text");n&&(n.textContent=""),null===(e=r.querySelector(".error-block-under-input"))||void 0===e||e.classList.remove("error-block-under-input--success"),r.querySelector(".error-block-under-input");const s=r.style.height,a=r.style.paddingTop;r.style.height="auto",r.style.paddingTop=`${this._anim.paddingTopOpened}px`;const l=r.clientHeight;if(r.style.height=s||"0px",r.style.paddingTop=a||"0px",parseFloat(r.style.height)===l&&1===parseFloat(r.style.opacity||"1"))this._ringIcon(r);else{if(this._cancelAnimations(r),this._canAnimate(r)){r.animate([{height:`${parseFloat(r.style.height)||0}px`,paddingTop:`${parseFloat(r.style.paddingTop)||0}px`,opacity:parseFloat(r.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",(()=>{r.style.height="auto",r.style.paddingTop=`${this._anim.paddingTopOpened}px`,r.style.opacity="1"}))}else r.style.height="auto",r.style.paddingTop=`${this._anim.paddingTopOpened}px`,r.style.opacity="1";this._ringIcon(r)}this.$el.addEventListener("input",(()=>this.hideErrorForm()),{passive:!0,once:!0})}hideErrorForm(){const t=this.$el.querySelectorAll(this.config.inputWrapperSelector),e=t[t.length-1].nextElementSibling;if(!e||!e.classList.contains("error-block-under-input__wrapper"))return;if((""===e.style.height||"auto"===e.style.height)&&(e.style.height=`${e.clientHeight}px`),e.style.paddingTop=e.style.paddingTop||`${this._anim.paddingTopOpened}px`,e.style.opacity=e.style.opacity||"1",this._cancelAnimations(e),this._canAnimate(e)){e.animate([{height:`${parseFloat(e.style.height)||e.clientHeight}px`,paddingTop:`${parseFloat(e.style.paddingTop)||this._anim.paddingTopOpened}px`,opacity:parseFloat(e.style.opacity||"1")},{height:"0px",paddingTop:"0px",opacity:0}],{duration:this._anim.duration,easing:this._anim.easing,fill:"forwards"}).addEventListener("finish",(()=>{e.remove()}))}else e.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").forEach((t=>{t instanceof HTMLInputElement&&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)}sendData(){return e(this,void 0,void 0,(function*(){var t;const e=this.$el.getAttribute("action")||"",o=(null===(t=this.$el.getAttribute("method"))||void 0===t?void 0:t.toUpperCase())||"POST",r=this.$el.getAttribute("enctype")||"application/x-www-form-urlencoded";let n=null,s={};const a=i(this.$el);let l={};if(a.forEach(((t,e)=>{l[e]=t})),this.config.logging)for(const t of a.keys())console.log(a.get(t));switch("function"==typeof this.config.wrapData&&(l=this.config.wrapData(l)),r){case"application/x-www-form-urlencoded":n=new URLSearchParams(i(this.$el)).toString(),s["Content-Type"]="application/x-www-form-urlencoded";break;case"multipart/form-data":n=i(this.$el);break;case"text/plain":n=Array.from(i(this.$el)).map((([t,e])=>`${encodeURIComponent(t)}=${encodeURIComponent(e)}`)).join("\n"),s["Content-Type"]="text/plain";break;case"application/json":n=JSON.stringify(l),s["Content-Type"]="application/json";break;default:console.warn(`Неизвестный enctype: ${r}. Используется application/x-www-form-urlencoded.`),n=new URLSearchParams(i(this.$el)).toString(),s["Content-Type"]="application/x-www-form-urlencoded"}return fetch(e,{method:o,body:n,headers:s})}))}submit(){return e(this,void 0,void 0,(function*(){var t,e,i,o,r,n,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;const l=yield this.sendData(),c=yield l.json();if(this.waitResponse=!1,!0===this.config.showLoaderButton&&this.hideLoader(),null===(o=(i=this.config).onResponse)||void 0===o||o.call(i,c,this),200===l.status){if(!0===c.success)null===(n=(r=this.config).onResponseSuccess)||void 0===n||n.call(r,c,this);else if(null===(a=(s=this.config).onResponseUnsuccess)||void 0===a||a.call(s,c,this),!0===c.error&&(c["error-msg"]&&this.showErrorForm(c["error-msg"]),c.errors)){const t=[];c.errors.forEach((e=>{const i=this.$el.querySelector(`input[name="${e.name}"]`);i?(t.push(i),this.showError(i,e["error-msg"])):console.warn(`Не найдено поле с именем: ${e.name}, для вывода ошибки: ${e["error-msg"]}`)})),!0===this.config.scrollToFirstErroredInput&&this.scrollToFirstErroredInput(t)}!function(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"])}(c)}}}))}}return t.default=h,t.getAllValidators=function(){return new Map(n)},t.getValidator=a,t.registerValidator=s,Object.defineProperty(t,"__esModule",{value:!0}),t}({});
|
|
2
|
-
//# sourceMappingURL=FormFather.min.js.map
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|