Custom Forms / Формы

ver.4.1.1
от 15.07.2025 16:18

Особенности

  1. Кастомизация настроек
    Переопределение параметры при инициализации скрипта.
  2. Блокировка кнопки "отправить" если не заполнены обязательные поля.
    Можно задать как для всех форм сразу, так и для конкретной формы.
  3. Добавление '*' в label и в placeholder у обязательных полей.
    Чтобы вручную в html коде не писать.
  4. Валидация полей при клике по кнопке "отправить форму".
  5. Добавление и удаление класса is-error об ошибке поля.
  6. Добавление и удаление сообщения об ошибке поля.
  7. Применение масок к текстовым полям.
    Например: Телефон, номер и серия паспорта, инн, снилс, огрн, огрнип, кпп, бик, платежный реквизит.
  8. Отображение класса is-focused у текстового поля.
    Когда в поле установлен курсор или введет текст. Отображается кроме чекбоксов и радио кнопок.
  9. Применение стилизации к полю загрузки файлов.
  10. Серилизация полей формы перед отправкой данных на сервер.
  11. Отправка данных формы на сервер.
    В настройках указываем адрес на который необходимо отправлять данные с формы. Можно задать как для всех форм сразу, так и для конкретной формы.
  12. Обновление токена капчи Google Recaptcha v3.
    При каждом нажатии на кнопку 'отправить форму' происходит обновление токена и проставление его в скрытое поле.
  13. Открытие модальных окон во время и после отправки формы.
  14. Отображение количества введенных символов.
  15. Отображение количества символов сколько осталось ввести.

Скриншоты

Дефолтный вид формы
Плавающие лейблы
Лейблы слева
Форма авторизации
Форма изменения пароля

Codes

Jade
Html
JS
По умолчанию
от 11.07.2025 15:34
Описание классов:
.form_type_default - Дефолтный классический вид формы. Когда лейблы находятся над текстовыми полями.
.form_type_labels-floating - Лейблы находятся поверх текстового поля и при получении фокуса подлетают вверх. Эффект как у google форм.
.form_type_labels-left - Лейблы располагатся слева от текстовых полей.
Код
      form.form.form_type_[default|labels-floating|labels-left].form-[ID](data-form-id=[ID])
    input.token_v3(type="hidden", name="g-recaptcha-response", value="")
    .form__fields
        .form__field.form__field-name
            label.form__label(for="form-ID-field-name") Имя
            input.form__input(id="form-ID-field-name", type="text", name="name", data-type="name", value="", placeholder="Имя", data-require)
        .form__field.form__field-phone
            label.form__label(for="form-ID-field-phone") Телефон
            input.form__input(id="form-ID-field-phone", type="text", name="phone", data-type="phone", value="", placeholder="Телефон", data-require)
        .form__field.form__field-email
            label.form__label(for="form-ID-field-email") Почта
            input.form__input(id="form-ID-field-email", type="text", name="email", data-type="email", value="", placeholder="Почта", data-require)
        .form__field.form__field-comment
            label.form__label(for="form-ID-field-comment") Сообщение
            textarea.form__input(id="form-ID-field-comment", name="comment", data-type="comment", placeholder="Сообщение")
        .form__field.form__field-position
            label.form__label(for="form-ID-field-position") Должность
            input.form__input(id="form-ID-field-position", type="text", name="position", data-type="position", value="", placeholder="Должность")
        .form__field.form__field-company-name
            label.form__label(for="form-ID-field-company-name") Компания
            input.form__input(id="form-ID-field-company-name", type="text", name="company-name", data-type="company-name", value="", placeholder="Компания")
        .form__field.form__field-inn
            label.form__label(for="form-ID-field-inn") Инн
            input.form__input(id="form-ID-field-inn", type="text", name="inn", data-type="inn", value="", placeholder="Инн")
        .form__field.form__field-passport-number
            label.form__label(for="form-ID-field-passportNumber") Серия, номер паспорта
            input.form__input(id="form-ID-field-passportNumber", type="text", name="passportNumber", data-type="passportNumber", value="", placeholder="Серия, номер паспорта")
        .form__field.form__field-passport-division
            label.form__label(for="form-ID-field-passport-division") Код подразделения
            input.form__input(id="form-ID-field-passport-division", type="text", name="passportDivision", data-type="passportDivision", value="", placeholder="Код подразделения")
        .form__field.form__field-snils
            label.form__label(for="form-ID-field-snils") Cнилс
            input.form__input(id="form-ID-field-snils", type="text", name="snils", data-type="snils", value="", placeholder="Cнилс")
        .form__field.form__field-ogrn
            label.form__label(for="form-ID-field-ogrn") ОГРН
            input.form__input(id="form-ID-field-ogrn", type="text", name="ogrn", data-type="ogrn", value="", placeholder="ОГРН")
        .form__field.form__field-ogrnip
            label.form__label(for="form-ID-field-ogrnip") ОГРНИП
            input.form__input(id="form-ID-field-ogrnip", type="text", name="ogrnip", data-type="ogrnip", value="", placeholder="ОГРНИП")
        .form__field.form__field-kpp
            label.form__label(for="form-ID-field-kpp") КПП
            input.form__input(id="form-ID-field-kpp", type="text", name="kpp", data-type="kpp", value="", placeholder="КПП")
        .form__field.form__field-bik
            label.form__label(for="form-ID-field-bik") БИК
            input.form__input(id="form-ID-field-bik", type="text", name="bik", data-type="bik", value="", placeholder="БИК")
        .form__field.form__field-payment-account
            label.form__label(for="form-ID-field-payment-account") Расчетный счет
            input.form__input(id="form-ID-field-payment-account", type="text", name="paymentAccount", data-type="paymentAccount", value="", placeholder="Расчетный счет")
        .form__field.form__field-individual-and-legal-entity-selection
            .form__input-wrapper
                .radio.radio_type_default.radio_theme_default
                    input.form__input.radio__input(id="form-ID-field-individual", type="radio", name="individual-and-legal-entity-selection")
                    label.form__label.radio__label(for="form-ID-field-individual") Физическое лицо
            .form__input-wrapper
                .radio.radio_type_default.radio_theme_default
                    input.form__input.radio__input(id="form-ID-field-legal", type="radio", name="individual-and-legal-entity-selection")
                    label.form__label.radio__label(for="form-ID-field-legal") Юридическое лицо
        .form__field.form__field-agreement-privace-policy
            .checkbox.checkbox_type_default.checkbox_theme_default
                input.form__input.checkbox__input(id="form-ID-field-agreement-privace-policy", type="checkbox", name="agreement-privace-policy", data-type="agreement", data-require)
                label.form__label.checkbox__label(for="form-ID-field-agreement-privace-policy")
                    span Согласен(сна) с условиями <a href="/" target="_blank">политики конфиденциальности</a>.
        .form__field.form__field-agreement-personal-data
            .checkbox.checkbox_type_default.checkbox_theme_default
                input.form__input.checkbox__input(id="form-ID-field-agreement-personal-data", type="checkbox", name="agreement-personal-data", data-type="agreement", data-require)
                label.form__label.checkbox__label(for="form-ID-field-agreement-personal-data")
                    span Согласен(сна) на <a href="/" target="_blank">обработку персональных данных</a>
        .form__field.form__field-rules-agreement.form__field-mailing-consent
            .checkbox.checkbox_type_default.checkbox_theme_default
                input.form__input.checkbox__input(id="form-ID-field-mailing-consent", type="checkbox", name="mailing-consent", data-type="agreement", data-require)
                label.form__label.checkbox__label(for="form-ID-field-mailing-consent")
                    span Согласен(сна) на рассылку
        .form__note
            span *
            |  обязательные для заполнения поля
    .form__actions
        button.form__submit.button.button-primary.button-m(type="submit") Отправить
    
Форма авторизации
от 11.07.2025 15:34
Код
      form.form.form_type_default.form-login(data-form-id="login")
    input.token_v3(type="hidden", name="g-recaptcha-response", value="")
    .form__fields
        .form__field.form__field-login
            label.form__label(for="form-login-field-login") Логин
            input.form__input(id="form-login-field-login", type="text", name="login", data-type="login", value="", placeholder="Логин", data-require)
        .form__field.form__field-password
            label.form__label(for="form-login-field-password") Пароль
            .form__input-wrapper
                input.form__input(id="form-login-field-password", type="password", name="password", data-type="password", value="", placeholder="Пароль", data-require)
        .form__field.form__field-remember-me
            .checkbox.checkbox_type_default.checkbox_theme_default
                input.form__input.checkbox__input(id="form-login-field-remember-me", type="checkbox", name="remember-me")
                label.form__label.checkbox__label(for="form-login-field-remember-me")
                    span Запомнить меня
        .form__note
            span *
            |  обязательные для заполнения поля
    .form__actions
        button.form__submit.button.button-primary.button-m(type="submit") Войти
        button.form__submit.button.button-primary.button-m(type="button") Зарегистрироваться
    
Форма изменения пароля
от 11.07.2025 15:34
Код
      form.form.form_type_default.form-change-password(data-form-id="change-password")
    input.token_v3(type="hidden", name="g-recaptcha-response", value="")
    .form__fields
        .form__field.form__field-login
            label.form__label(for="form-change-password-field-login") Логин
            input.form__input(id="form-change-password-field-login", type="text", name="login", data-type="login", value="", placeholder="Логин", data-require)
        .form__field.form__field-password
            label.form__label(for="form-change-password-field-password") Пароль
            .form__input-wrapper
                input.form__input(id="form-change-password-field-password", type="password", name="password", data-type="password", value="", placeholder="Пароль", data-require)
        .form__field.form__field-password-confirm
            label.form__label(for="form-change-password-field-passwordConfirm") Повторите пароль
            .form__input-wrapper
                input.form__input(id="form-change-password-field-passwordConfirm", type="password", name="passwordConfirm", data-type="passwordConfirm", value="", placeholder="Повторите пароль", data-require)
        .form__note
            span *
            |  обязательные для заполнения поля
    .form__actions
        button.form__submit.button.button-primary.button-m(type="submit") Изменить пароль
    

Changelogs

v4.1.1
latest
от 15 Июля 2025
  • Исправление ошибок.
v4.1.0
от 6 Февраля 2025
  • Отображение количества введенных символов.
  • Отображение количества символов сколько осталось ввести.
  • Сделал добавление группы иконок в поле. Теперь иконки счетчика и отображение символов находятся в одном диве.
v4.0.0
от 29 Января 2025
  • Валидация полей (номер и серия паспорта, инн, снилс, огрн, огрнип, кпп, бик, платежный реквизит).
  • Маска для полей (номер и серия паспорта, инн, снилс, огрн, огрнип, кпп, бик, платежный реквизит).
v3.0.0
  • Информация отсутствует.
v2.0.0
  • Информация отсутствует.
v1.0.0
  • Валидация полей (телефон, почта, чекбокс политики), а также если поле не заполнено.
  • Блокировка кнопки "отправить" если не заполнены обязательные поля.
  • Отображение класса is-focused у текстового поля.
  • Маска для полей (телефон).

Архив

ver.4.1.0
JS
от 11.07.2025 15:34
Параметры
PARAMTYPEDEFAULTDESCRIPTION
blockedButtonSubmitbooleantrueБлокируется кнопка отправки до тех пор пока не будут заполнены обязательные поля.
formsobject
{
    default: {
        ajax: 'ajax/form.php',
        blockingSendButton: true
    }
}

Список форм.

Описание параметров:
ajaxУрл адрес для отправки данных с формы, для проверки валидности на стороне сервера.
blockingSendButtonБлокируется кнопка отправки до тех пор пока не будут заполнены обязательные поля.

Можно добавить свою форму в этот список с указанием имени формы. Имя формы можно взять из атрибута data-form-id в теге <form>

Например:
login: {
    ajax: 'ajax/form-login.php',
    blockingSendButton: false
},
register: {
    ajax: 'ajax/form-register.php',
    blockingSendButton: false
},
restorePassword: {
    ajax: 'ajax/form-restore-password.php',
    blockingSendButton: false
}
fieldsobject
{
    phone: {
        mask: true
    },
    password: {
        min: 6,
        max: 10
    }
}

Поля формы

Имена полей:
Используются в атрибутах "name" и "data-type"

loginЛогин
passwordПароль
passwordConfirmПовторный пароль
phoneТелефон
emailПочта
commentСообщение/Комментарий
agreementСогласие с политикой
passportNumberСерия и номер паспорта
passportDivisionКод подразделения
innИНН
snilsСнилс
ogrnОГРН
ogrnipОГРНИП
kppКПП
bikБИК
paymentAccountРасчетный счет

{
    phone: {
        mask: true
    },
    password: {
        min: 6,
        max: 10
    },
    comment: {
        displayNumberCharacterEntered: false,
        displayLimitNumberCharacterEntered: false,
        max: 150,
    }
}
Имена полей:

displayNumberCharacterEnteredотображение количества введенных символов (можно указать в любом поле)
displayLimitNumberCharacterEnteredотображение сколько осталось ввести символов (можно указать в любом поле)
maxмаксимальное количество символов

captchaobject | false
{
    key: '6LeE740l',
    classInput: 'token_v3',
}

Google reCaptcha v3

Описание параметров:
keyКлюч
classInputКласс скрытого поля куда записывать токен капчи
popupsobject
{
    classSuccessful: 'popup-success',
    classError: 'popup-error',
    classOpened: 'is-opened'
}

Модальные окна

Описание параметров:
classSuccessfulКласс модального окна с сообщением об успешной отправки формы.
classErrorКласс модального окна с сообщением об ошибке отправки формы.
classOpenedКласс для модального окна который активирует видимость модалки.
Как юзать
      new CustomForm();
    
Код
      const CustomForm = function (customSettings) {
    this.deepMergeObjects = function (obj1, obj2) {
        const result = {};
        for (const key in obj2) {
            if (obj2.hasOwnProperty(key)) {
                if (typeof obj2[key] === "object" && obj1.hasOwnProperty(key) && typeof obj1[key] === "object") {
                    result[key] = this.deepMergeObjects(obj1[key], obj2[key]);
                } else {
                    result[key] = obj2[key];
                }
            }
        }
        for (const key in obj1) {
            if (obj1.hasOwnProperty(key) && !result.hasOwnProperty(key)) {
                if (typeof obj1[key] === "object") {
                    result[key] = this.deepMergeObjects(obj1[key], {});
                } else {
                    result[key] = obj1[key];
                }
            }
        }
        return result;
    }

    const FORM = document.querySelectorAll('form');
    let elemGroupFieldIcon = null;

    let errors = false;

    const DEFAULT_SETTINGS = {
        blockedButtonSubmit: true,
        captcha: {
            key: '6LeE740lAAAAAMpDp4bvjLC9CAxlY6QTo_lFiXOy',
            classInput: 'token_v3',
        },
        fields: {
            phone: {
                mask: true
            },
            password: {
                min: 6,
                max: 10
            },
            comment: {
                displayNumberCharacterEntered: false,
                displayLimitNumberCharacterEntered: false,
                max: 150,
            }
        },
        popups: {
            classSuccessful: 'popup-success',
            classError: 'popup-error',
            classOpened: 'is-opened'
        },
        forms: {
            default: {
                ajax: 'ajax/form.php',
                blockingSendButton: false
            },
        },
    }

    let settings = this.deepMergeObjects(DEFAULT_SETTINGS, customSettings);

    let formData = {};

    this.init = function () {
        FORM.forEach(form => {
            if (form) {
                const idForm = form.getAttribute('data-form-id');

                // блокируем кнопку отправить если поля не заполнены
                if (settings.forms[idForm] && settings.forms[idForm].hasOwnProperty('blockingSendButton') ? settings.forms[idForm].blockingSendButton : settings.forms.default.blockingSendButton) {
                    this.setBlockedButtonSubmit(form);
                }

                // добавляем событие на кнопку отправить
                form.addEventListener('submit', e => this.handlerOnSubmitForm(e, form));

                // пробегаемся по полям:
                // 1. добавляем класс "is-focused" при установки курсора и введенного текста
                // 2. добавляем события "focus", "blur", "input"
                // 3. применяем стилизацию для поля "file"
                // 4. устанавливаем "*" в лейбле поля и в плейсхолдере
                form.querySelectorAll('.form__field').forEach(field => {
                    let element = field.querySelector(".form__input");

                    this.addingCharacterDisplay(field);
                    this.addMasked(element);

                    // Счетчик количества введенных символов
                    this.getNumberEnteredCharacters(field, element);

                    if (element.value !== '' && element.getAttribute('type') !== 'checkbox' && element.getAttribute('type') !== 'radio') {
                        if (form.classList.contains('form-with-hidding-label')) {
                            field.querySelector('.form__label').classList.add('is-hidden');
                        } else {
                            field.classList.add('is-focused');
                        }
                    }
                    element.addEventListener('focus', e => this.handlerFocusOnLabel(e, form, field));
                    element.addEventListener('blur', e => this.handlerBlurOnLabel(e, form, field));
                    element.addEventListener('input', e => this.handlerInputOnInput(e, form, field, element/*, dataType*/));
                    element.getAttribute('type') === 'file' ? this.applyStylingFileUpload(element) : null;

                    // устанавливаем "*" в лейбле поля и в плейсхолдере
                    this.setLabelRequireOnField(field, element);
                });
            }
        });
    }
    this.setLabelRequireOnField = function (field, element) {
        // устанавливаем "*" в лейбле поля и в плейсхолдере
        if (element.hasAttribute("data-require") && !this.checkingValueAttributeTypeOnInput(element, 'checkbox')) {
            field.querySelector('.form__label').innerHTML += '<span class="form__require">*</span>';
            if (this.checkingStockAttributePlaceholderOnInput(element)) {
                element.setAttribute('placeholder', element.getAttribute('placeholder') + '*');
            }
        }
    }
    this.checkingStockAttributeRequireOnInput = function (element) {
        return element.hasAttribute('data-require');
    }
    this.checkingStockAttributePlaceholderOnInput = function (element) {
        return element.hasAttribute('placeholder');
    }
    this.checkingValueAttributeTypeOnInput = function (element, type) {
        return element.getAttribute('type') === type;
    }
    this.setBlockedButtonSubmit = function (form) {
        let filled = {};
        form.querySelector('.form__submit').disabled = true;
        form.querySelectorAll('.form__field').forEach(field => {
            const element = field.querySelector(".form__input");
            if (element.hasAttribute("data-require")) {
                // if (element.getAttribute("data-type") === 'agreement') {
                //     filled[element.getAttribute("name")] = true;
                // }
                element.addEventListener('input', e => this.handlerChangeOnRequireField(e, form, filled));
            }
        });
    }
    this.handlerChangeOnRequireField = function (e, form, filled) {
        // console.clear();
        let element = e.target,
            name = e.target.getAttribute('name'),
            type = e.target.getAttribute('data-type');
        switch (type) {
            case 'phone':
                if (e.target.value.match(/\d+/g) && (e.target.value.match( /\d+/g ).join('')).length === 11) {
                    filled[name] = true;
                } else {
                    delete filled[name];
                }
                // if (e.target.value !== '' && e.target.value !== ' (___) ___-__-__' && e.target.value !== '+ (___) ___-__-__' && e.target.value !== '+7(___)___-__-__') {
                //     filled[name] = true;
                // } else if (e.target.value === ' (___) ___-__-__' || e.target.value === '+ (___) ___-__-__' || e.target.value === '+7(___)___-__-__') {
                //     delete filled[name];
                // } else {
                //     delete filled[name];
                // }
                break;
            case 'agreement':
                if (element.checked) {
                    filled[name] = true;
                } else {
                    delete filled[name];
                }
                break;
            default:
                if (e.target.value) {
                    filled[name] = true;
                } else {
                    delete filled[name];
                }
                break;
        }
        if (Object.keys(filled).length === this.getCountRequireField(form)) {
            form.querySelector('.form__submit').disabled = false;
        } else {
            form.querySelector('.form__submit').disabled = true;
        }
    }
    this.getCountRequireField = function (form) {
        return form.querySelectorAll('.form__input[data-require]').length;
    }
    this.validateField = function (form, field, element) {
        // const name = element.getAttribute("name");
        const type = element.getAttribute("data-type");
        switch (type) {
            case 'login':
                if (!formData.login.length) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Поле заполнено некорректно');
                }
                if (!/^[a-zA-Z0-9]+$/.test(formData.login)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Логин может содержать только буквы на латинице и цифры');
                }
                if (formData.login.length < 3) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Логин должен состоять не менее 3 символов');
                }
                break;
            case 'password':
                if (!formData.password.length) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Поле заполнено некорректно');
                }
                if (formData.password.length < settings.fields.password.min) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, `Пароль должен состоять не менее ${settings.fields.password.min} символов`);
                }
                if (formData.password.length > settings.fields.password.max) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, `Пароль должен состоять не более ${settings.fields.password.max} символов`);
                }
                if (!/^[a-zA-Z0-9]+$/.test(formData.password)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Пароль может содержать только буквы на латинице и цифры');
                }
                // if (!/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[a-zA-Z0-9]{6,10}$/.test(formData.password)) {
                //     errors = true;
                //     this.addClassOnError(field);
                //     this.addMessageOnError(field, 'Пароль должен содержать как минимум одну заглавную букву, одну строчную букву и одну цифру');
                // }
                break;
            case 'passwordConfirm':
                if (!formData.passwordConfirm.length) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Повторите пароль');
                }
                if (formData.password !== formData.passwordConfirm) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Пароли не совпадают');
                }
                break;
            case 'phone':
                const phonePattern = /^((8|\+374|\+994|\+995|\+375|\+7|\+380|\+38|\+996|\+998|\+993)[\- ]?)?\(?\d{3,5}\)?[\- ]?\d{1}[\- ]?\d{1}[\- ]?\d{1}[\- ]?\d{1}[\- ]?\d{1}(([\- ]?\d{1})?[\- ]?\d{1})?$/i;
                const phoneTest = !phonePattern.test(formData.phone);
                const phoneLength = (formData.phone.match( /\d+/g ).join('')).length > 1;

                if ((this.checkingStockAttributeRequireOnInput(element) && phoneTest) || (phoneLength && phoneTest)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, "Поле заполнено некорректно");
                }
                break;
            case 'email':
                const emailPattern = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
                const emailPatternSymbol = /^[a-zA-Z0-9.@-]+$/;
                const emailTest = !emailPattern.test(formData.email);
                const emailTestSymbol = !emailPatternSymbol.test(formData.email);
                const emailLength = formData.email.length;

                if ((this.checkingStockAttributeRequireOnInput(element) && emailTestSymbol) || (emailLength && emailTestSymbol)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Почта может содержать только буквы на латинице, цифры и символы: дефис и точка');
                }
                if ((this.checkingStockAttributeRequireOnInput(element) && emailTest) || (emailLength && emailTest)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, "Поле заполнено некорректно");
                }
                break;
            case 'inn':
                if ((formData.inn.length && formData.inn.length < 10) || this.checkingStockAttributeRequireOnInput(element)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, `ИНН должен состоять из 10 или 12 символов`);
                }
                break;
            case 'passportNumber':
                const passportNumberPattern = /\d{3}\s\d{6}/;
                const passportNumberTest = !passportNumberPattern.test(formData.passportNumber);
                const passportNumberLength = formData.passportNumber.length;
                if ((this.checkingStockAttributeRequireOnInput(element) && passportNumberTest) || (passportNumberLength && passportNumberTest)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, "Поле заполнено некорректно");
                }
                break;
            case 'passportDivision':
                const passportDivisionPattern = /^\d{3}\-\d{3}$/;
                const passportDivisionTest = !passportDivisionPattern.test(formData.passportDivision);
                const passportDivisionLength = formData.passportDivision.length;
                if ((this.checkingStockAttributeRequireOnInput(element) && passportDivisionTest) || (passportDivisionLength && passportDivisionTest)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, "Поле заполнено некорректно");
                }
                break;
            case 'snils':
                const snilsPattern = /^\d{3}-\d{3}-\d{3} \d{2}$/;
                const snilsTest = !snilsPattern.test(formData.snils);
                const snilsLength = formData.snils.length;
                if ((this.checkingStockAttributeRequireOnInput(element) && snilsTest) || (snilsLength && snilsTest)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, "Поле заполнено некорректно");
                }
                break;
            case 'ogrn':
                const ogrnPattern = /^[0-9]{13}$/;
                const ogrnTest = !ogrnPattern.test(formData.ogrn);
                const ogrnLength = formData.ogrn.length;
                if ((this.checkingStockAttributeRequireOnInput(element) && ogrnTest && ogrnLength.length < 13) || (ogrnLength && ogrnTest)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, "ОГРН должен состоять из 13 символов");
                }
                if (this.checkingStockAttributeRequireOnInput(element) && !ogrnLength) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, "Поле не может быть пустым");
                }
                break;
            case 'ogrnip':
                const ogrnipPattern = /^[0-9]{15}$/;
                const ogrnipTest = !ogrnipPattern.test(formData.ogrnip);
                const ogrnipLength = formData.ogrnip.length;
                if ((this.checkingStockAttributeRequireOnInput(element) && ogrnipTest && ogrnipLength.length < 15) || (ogrnipLength && ogrnipTest)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, "ОГРНИП должен состоять из 15 символов");
                }
                if (this.checkingStockAttributeRequireOnInput(element) && !ogrnipLength) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, "Поле не может быть пустым");
                }
                break;
            case 'kpp':
                const kppPattern = /^[0-9]{9}$/;
                const kppTest = !kppPattern.test(formData.kpp);
                const kppLength = formData.kpp.length;
                if ((this.checkingStockAttributeRequireOnInput(element) && kppTest && kppLength.length < 9) || (kppLength && kppTest)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, "КПП должен состоять из 9 символов");
                }
                if (this.checkingStockAttributeRequireOnInput(element) && !kppLength) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, "Поле не может быть пустым");
                }
                break;
            case 'bik':
                const bikPattern = /^[0-9]{9}$/;
                const bikTest = !bikPattern.test(formData.bik);
                const bikLength = formData.bik.length;
                if ((this.checkingStockAttributeRequireOnInput(element) && bikTest && bikLength.length < 9) || (bikLength && bikTest)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, "БИК должен состоять из 9 символов");
                }
                if (this.checkingStockAttributeRequireOnInput(element) && !bikLength) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, "Поле не может быть пустым");
                }
                break;
            case 'paymentAccount':
                const paymentAccountPattern = /^(?:[\. ]*\d){20}$/;
                const paymentAccountTest = !paymentAccountPattern.test(formData.paymentAccount);
                const paymentAccountLength = formData.paymentAccount.length;
                if ((this.checkingStockAttributeRequireOnInput(element) && paymentAccountTest && paymentAccountLength.length < 20) || (paymentAccountLength && paymentAccountTest)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, "Расчетный счет должен состоять из 20 символов");
                }
                if (this.checkingStockAttributeRequireOnInput(element) && !paymentAccountLength) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, "Поле не может быть пустым");
                }
                break;
            case 'agreement':
                if (!element.checked) {
                    errors = true;
                    this.addClassOnError(field);
                }
                break;
            default:
                if (type) {
                    if (!formData[type].length && this.checkingStockAttributeRequireOnInput(element)) {
                        errors = true;
                        this.addClassOnError(field);
                        this.addMessageOnError(field, "Поле заполнено некорректно");
                    }
                }
                break;
        }
    }
    this.serializeForm = function (form) {
        formData = {};
        try {
            new FormData(form).forEach((value, key) => {
                formData[key] = value.trim();
            });
            // this.fetchToSend();
        } catch (error) {
            console.log("Error:", error);
        }
    }
    this.validateForm = function (form, e) {
        this.serializeForm(form);
        errors = false;
        let warningElems = form.querySelectorAll(".form__field.is-error") || true;
        if (warningElems.length) {
            warningElems.forEach(function (warningElem) {
                return warningElem.classList.remove("is-error");
            });
        }
        form.querySelectorAll('.form__field').forEach(field => {
            let element = field.querySelector("input, textarea, select");
            // if (element.hasAttribute("data-require")) {
            this.validateField(form, field, element);
            // }
        });
        if (errors) {
            e.preventDefault();
        } else {
            e.preventDefault();
            // this.sendingFormData(form);
            if (typeof settings.captcha === "object") {
                // когда параметр 'captcha' = true, то делаем получение токена ти устанавливаем токен в скрытый инпут
                e.preventDefault();
                this.getCaptchaToken(form);
            } else {
                form.submit();
            }
        }
    }
    this.handlerOnSubmitForm = function (e, form) {
        this.validateForm(form, e);
    }
    this.addClassOnError = function (field) {
        field.classList.add("is-error");
    }
    this.removeClassOnError = function (field) {
        field.classList.remove("is-error");
    }
    this.addMessageOnError = function (field, message) {
        if (!field.querySelector('.form__error-message')) {
            field.insertAdjacentHTML('beforeend', `<div class="form__error-message">${message}</div>`);
        }
    }
    this.removeMessageOnError = function (field) {
        if (field.querySelector('.form__error-message')) {
            field.querySelector('.form__error-message').remove();
        }
    }
    this.addMasked = function (element) {
        switch (element.getAttribute('data-type')) {
            case "passportNumber":
                new IMask(element, {
                    mask: '0000 000000',
                    lazy: true,
                });
                break;
            case "passportDivision":
                new IMask(element, {
                    mask: '000-000',
                    lazy: true,
                });
                break;
            case "inn":
                new IMask(element, {
                    mask: '000000000000',
                    regex: '^\d{10}|\d{12}$',
                    lazy: true,
                });
                break;
            case "snils":
                new IMask(element, {
                    mask: '000-000-000 00',
                    lazy: true,
                });
                break;
            case "ogrn":
                new IMask(element, {
                    mask: '0000000000000',
                    lazy: true,
                });
                break;
            case "ogrnip":
                new IMask(element, {
                    mask: '000000000000000',
                    lazy: true,
                });
                break;
            case "kpp":
                new IMask(element, {
                    mask: '000000000',
                    lazy: true,
                });
                break;
            case "bik":
                new IMask(element, {
                    mask: '000000000',
                    lazy: true,
                });
                break;
            case "paymentAccount":
                new IMask(element, {
                    mask: '00000 000 0 0000 0000000',
                    lazy: true,
                });
                break;
            case "phone":
                if (settings.fields.phone.mask) {
                    new IMask(element, {
                        mask: "+{7} (000) 000-00-00",
                        lazy: false,
                    });
                    // new IMask(element, {
                    //     mask: [
                    //         {
                    //             mask: '+{0} (000) 000-00-00',
                    //             startsWith: '7',
                    //             lazy: false,
                    //             country: 'Russia'
                    //         },
                    //         {
                    //             mask: '{0} (000) 000-00-00',
                    //             startsWith: '8',
                    //             lazy: false,
                    //             country: 'Russia'
                    //         },
                    //     ],
                    //     dispatch: (appended, dynamicMasked) => {
                    //         const number = (dynamicMasked.value + appended).replace(/\D/g, '');
                    //         return dynamicMasked.compiledMasks.find(m => number.indexOf(m.startsWith) === 0);
                    //     }
                    // })
                }
                break;
            default:
                break;
        }
    }
    this.handlerFocusOnLabel = function (e, form, field) {
        if (field.querySelector('.form__label') && field.querySelector('.form__input').getAttribute('type') !== 'checkbox') {
            field.classList.add('is-focused');
            if (form.classList.contains('form-with-hidding-label')) {
                field.querySelector('.form__label').classList.add('is-hidden');
            }
        }
    }
    this.handlerBlurOnLabel = function (e, form, field) {
        if (field.querySelector('.form__label') && e.currentTarget.value === '') {
            field.querySelector('.form__label').classList.remove('is-hidden');
            field.classList.remove('is-focused');
        }
    }
    this.handlerInputOnInput = function (e, form, field, element) {
        this.removeClassOnError(field);
        this.removeMessageOnError(field);

        // Счетчик количества введенных символов
        this.getNumberEnteredCharacters(field, element);
    }
    this.applyStylingFileUpload = function (element) {
        $(element).simpleFileInput({
            placeholder: 'Прикрепить файл',
            buttonText: '',
            width: 'false',
        });
    }
    this.getCaptchaToken = function (form) {
        grecaptcha.ready(() => {
            grecaptcha.execute(settings.captcha.key, {
                action: 'add_form'
            })
                .then(token => {
                    this.setCaptchaToken(form, token);
                });
        });
    }
    this.setCaptchaToken = function (form, token) {
        form.querySelector(`.${settings.captcha.classInput}`).value = token;
        this.serializeForm(form);
        this.sendingFormData(form);
    }
    this.sendingFormData = function (form) {
        const idForm = form.getAttribute('data-form-id');
        const referenceOnObject = settings.forms[idForm] && settings.forms[idForm].hasOwnProperty('ajax') ? idForm : 'default';
        const data = settings.forms[referenceOnObject];
        fetch(data.ajax, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            body: new URLSearchParams(formData).toString()
        })
            .then(response => {
                if (!response.ok) {
                    throw new Error('Ошибка запроса');
                }
                return response.json();
            })
            .then(data => {
                if (data.success) {
                    // console.log('Заявка успешно отправлена');
                    this.openPopupSuccessfulSending();
                } else {
                    this.openPopupErrorSending();
                }
            })
            .catch(error => {
                this.openPopupErrorSending();
                console.log(error);
            });
    }
    this.openPopupSuccessfulSending = function (form) {
        document.body.querySelector(`.${settings.popups.classSuccessful}`).classList.add(settings.popups.classOpened);
    }
    this.openPopupErrorSending = function (form) {
        document.body.querySelector(`.${settings.popups.classError}`).classList.add(settings.popups.classOpened);
    }

    // Иконка показать/скрыть введенные символы
    this.addingCharacterDisplay = function (field) {
        let element = field.querySelector(".form__input");
        if (element.getAttribute('data-type') === 'password' || element.getAttribute('data-type') === 'passwordConfirm') {
            this.addingGroupFieldIcons(field);
            elemGroupFieldIcon.insertAdjacentHTML('beforeend', `
                <div class="form__characters-display-icon">
                    <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M8 5.00082C6.34357 5.00082 5.00077 6.34362 5.00077 8.00005C5.00077 9.65647 6.34357 10.9993 8 10.9993C9.65642 10.9993 10.9992 9.65647 10.9992 8.00005C10.9992 6.34362 9.65642 5.00082 8 5.00082ZM6.00077 8.00005C6.00077 6.89591 6.89586 6.00082 8 6.00082C9.10414 6.00082 9.99922 6.89591 9.99922 8.00005C9.99922 9.10419 9.10414 9.99927 8 9.99927C6.89586 9.99927 6.00077 9.10419 6.00077 8.00005Z" fill="#222222"/><path fill-rule="evenodd" clip-rule="evenodd" d="M8 2.75C4.59429 2.75 1.68254 4.85238 0.531638 7.81917C0.486509 7.9355 0.486509 8.0645 0.531638 8.18084C1.68254 11.1476 4.59429 13.25 8 13.25C11.4057 13.25 14.3175 11.1476 15.4684 8.18083C15.5135 8.0645 15.5135 7.9355 15.4684 7.81917C14.3175 4.85238 11.4057 2.75 8 2.75ZM8 12.25C5.08376 12.25 2.58765 10.4929 1.53711 8C2.58765 5.50712 5.08376 3.75 8 3.75C10.9162 3.75 13.4123 5.50712 14.4629 8C13.4123 10.4929 10.9162 12.25 8 12.25Z" fill="#222222"/></svg>
                </div>
            `);
            field.querySelector('.form__characters-display-icon').addEventListener('click', e => this.handlerClickOnCharacterDisplay(e, field, element));
        }
    }
    this.handlerClickOnCharacterDisplay = function (e, field, element) {
        const icon = field.querySelector('.form__characters-display-icon');
        icon ? icon.innerHTML = '' : null;
        if (element.getAttribute('type') === 'password') {
            element.setAttribute('type', 'text');
            this.replaceIconInCharacterDisplay(icon, 'hide');
        } else {
            element.setAttribute('type', 'password');
            this.replaceIconInCharacterDisplay(icon, 'show');
        }
    }
    this.replaceIconInCharacterDisplay = function (icon, value) {
        if (value === 'show') {
            icon.insertAdjacentHTML('beforeend', `
                <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M8 5.00082C6.34357 5.00082 5.00077 6.34362 5.00077 8.00005C5.00077 9.65647 6.34357 10.9993 8 10.9993C9.65642 10.9993 10.9992 9.65647 10.9992 8.00005C10.9992 6.34362 9.65642 5.00082 8 5.00082ZM6.00077 8.00005C6.00077 6.89591 6.89586 6.00082 8 6.00082C9.10414 6.00082 9.99922 6.89591 9.99922 8.00005C9.99922 9.10419 9.10414 9.99927 8 9.99927C6.89586 9.99927 6.00077 9.10419 6.00077 8.00005Z" fill="#222222"/><path fill-rule="evenodd" clip-rule="evenodd" d="M8 2.75C4.59429 2.75 1.68254 4.85238 0.531638 7.81917C0.486509 7.9355 0.486509 8.0645 0.531638 8.18084C1.68254 11.1476 4.59429 13.25 8 13.25C11.4057 13.25 14.3175 11.1476 15.4684 8.18083C15.5135 8.0645 15.5135 7.9355 15.4684 7.81917C14.3175 4.85238 11.4057 2.75 8 2.75ZM8 12.25C5.08376 12.25 2.58765 10.4929 1.53711 8C2.58765 5.50712 5.08376 3.75 8 3.75C10.9162 3.75 13.4123 5.50712 14.4629 8C13.4123 10.4929 10.9162 12.25 8 12.25Z" fill="#222222"/></svg>
            `);
        } else {
            icon.insertAdjacentHTML('beforeend', `
                <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M2.35355 1.64645C2.15829 1.45118 1.84171 1.45118 1.64645 1.64645C1.45118 1.84171 1.45118 2.15829 1.64645 2.35355L3.44673 4.15383C2.13656 5.05128 1.11099 6.3257 0.531638 7.81917C0.486509 7.9355 0.486509 8.0645 0.531638 8.18084C1.68254 11.1476 4.59429 13.25 8 13.25C9.32103 13.25 10.5677 12.9337 11.6662 12.3733L13.6464 14.3536C13.8417 14.5488 14.1583 14.5488 14.3536 14.3536C14.5488 14.1583 14.5488 13.8417 14.3536 13.6464L2.35355 1.64645ZM10.9171 11.6242L9.73774 10.4448C9.24747 10.794 8.64771 10.9993 8 10.9993C6.34357 10.9993 5.00077 9.65647 5.00077 8.00005C5.00077 7.35233 5.20609 6.75258 5.5552 6.26231L4.1679 4.87501C3.00128 5.62899 2.07776 6.71706 1.53711 8C2.58765 10.4929 5.08376 12.25 8 12.25C9.04151 12.25 10.0294 12.0259 10.9171 11.6242ZM6.27749 6.9846C6.10167 7.28221 6.00077 7.62935 6.00077 8.00005C6.00077 9.10419 6.89586 9.99927 8 9.99927C8.3707 9.99927 8.71784 9.89838 9.01545 9.72255L6.27749 6.9846Z" fill="#222222"/><path d="M7.71157 5.01451L8.92349 6.22643C9.28655 6.41586 9.58419 6.7135 9.77361 7.07656L10.9855 8.28848C10.9946 8.19356 10.9992 8.09734 10.9992 8.00005C10.9992 6.34362 9.65642 5.00082 8 5.00082C7.9027 5.00082 7.80649 5.00546 7.71157 5.01451Z" fill="#222222"/><path d="M14.4629 8C14.107 8.84447 13.5853 9.60451 12.9375 10.2404L13.6446 10.9475C14.4351 10.1696 15.0617 9.22921 15.4684 8.18083C15.5135 8.0645 15.5135 7.9355 15.4684 7.81917C14.3175 4.85238 11.4057 2.75 8 2.75C7.22285 2.75 6.47142 2.85947 5.76076 3.06371L6.58756 3.8905C7.04368 3.79839 7.51605 3.75 8 3.75C10.9162 3.75 13.4123 5.50712 14.4629 8Z" fill="#222222"/></svg>
            `);
        }
    }

    // Счетчик количества введенных символов
    this.getNumberEnteredCharacters = function (field, element) {
        let dataType = element.getAttribute("data-type");
        let quantity = element.value.length;

        if (settings.fields[dataType] && settings.fields[dataType].displayNumberCharacterEntered && !settings.fields[dataType].displayLimitNumberCharacterEntered) {
            !field.querySelector('.form__counter-entered-characters') ? this.addingNumberCharactersEntered(field,  quantity) : this.updatingNumberCharacters(field, quantity);
        }
        if (settings.fields[dataType] && !settings.fields[dataType].displayNumberCharacterEntered && settings.fields[dataType].displayLimitNumberCharacterEntered) {
            quantity = settings.fields[dataType].max - quantity;
            !field.querySelector('.form__counter-entered-characters') ? this.addingRemainingNumberCharacters(field,  quantity) : this.updatingNumberCharacters(field, quantity);
            quantity < 0 ? this.addClassOnErrorRemainingNumberCharacters(field) : this.removeClassOnErrorRemainingNumberCharacters(field);
        }
    }
    this.addingNumberCharactersEntered = function (field, value) {
        this.addingGroupFieldIcons(field);
        elemGroupFieldIcon.insertAdjacentHTML('beforeend', `<div class="form__counter-entered-characters">${value}</div>`);
    }
    this.updatingNumberCharacters = function (field, value) {
        field.querySelector('.form__counter-entered-characters').textContent = value;
    }
    this.addingRemainingNumberCharacters = function (field, value) {
        this.addingGroupFieldIcons(field);
        elemGroupFieldIcon.insertAdjacentHTML('beforeend', `<div class="form__counter-entered-characters">${value}</div>`);
    }
    this.addClassOnErrorRemainingNumberCharacters = function (field) {
        field.querySelector('.form__counter-entered-characters').classList.add("is-error");
    }
    this.removeClassOnErrorRemainingNumberCharacters = function (field) {
        field.querySelector('.form__counter-entered-characters').classList.remove("is-error");
    }

    /* Дополнительные элементы в полях */
    this.addingGroupFieldIcons = function (field) {
        if (!field.contains(field.querySelector('.form__group-field-icons'))) {
            if (field.querySelector('.form__input-wrapper')) {
                field.querySelector('.form__input-wrapper').insertAdjacentHTML('beforeend', `<div class="form__group-field-icons"></div>`);
                elemGroupFieldIcon = field.querySelector('.form__group-field-icons');
            }
        }
    }

    if (FORM) {
        this.init();
    }
}
    
ver.3.0.0
JS
Параметры
PARAMTYPEDEFAULTDESCRIPTION
blockedButtonSubmittrue | falsefalseБлокируется кнопка отправки до тех пор пока не будут заполнены обязательные поля.
phoneMasktrue | falsetrueМаска для телефона.
captchafalseИспользуется ли гугл капча или нет.
captchaKeystring-Ключ гугл капчи.
captchaInputClassstringtoken_v3Класс скрытого поля куда записывать токен капчи.
urlSendingFormDatastringajax/form.phpУрл адрес для отправки данных с формы, для проверки валидности на стороне сервера.
classPopupSuccessfulstringpopup-successКласс модального окна с сообщением об успешной отправки формы.
classPopupErrorstringpopup-errorКласс модального окна с сообщением об ошибке отправки формы.
classActiveOnPopupstringis-openedКласс для модального окна который активирует видимость модалки.
Как юзать
      new Form();
    
Код
      const Form = function (settings) {
    const FORM = document.querySelectorAll('form');

    let errors = false;

    const SETTINGS = {
        blockedButtonSubmit: false,
        phoneMask: true,
        captcha: false,
        captchaKey: '6LeE740lAAAAAMpDp4bvjLC9CAxlY6QTo_lFiXOy',
        captchaInputClass: 'token_v3',
        urlSendingFormData: 'ajax/form.php',
        classPopupSuccessful: 'popup-success',
        classPopupError: 'popup-error',
        classActiveOnPopup: 'is-opened',
    }

    let formData = {};

    this.init = function () {
        FORM.forEach(form => {
            if (form) {
                // проверка параметра "режима блокировки кнопки отправки"
                if (this.getParameter('blockedButtonSubmit')) {
                    this.setBlockedButtonSubmit(form);
                } else {
                    form.addEventListener('submit', e => this.handlerOnSubmitForm(e, form));
                }

                // проверка параметра "маски телефона"
                if (this.getParameter('phoneMask')) {
                    form.querySelector('[data-type="phone"]') ? this.addMaskPhone(form.querySelector('[data-type="phone"]')) : null;
                }

                // пробегаемся по полям:
                // 1. добавляем класс "is-focused" при установки курсора и введенного текста
                // 2. добавляем события "focus", "blur", "input"
                // 3. применяем стилизацию для поля "file"
                // 4. устанавливаем "*" в лейбле поля и в плейсхолдере
                form.querySelectorAll('.form__field').forEach(field => {
                    let element = field.querySelector(".form__input");
                    if (element.value !== '' && element.getAttribute('type') !== 'checkbox' && element.getAttribute('type') !== 'radio') {
                        if (form.classList.contains('form-with-hidding-label')) {
                            field.querySelector('.form__label').classList.add('is-hidden');
                        } else {
                            field.classList.add('is-focused');
                        }
                    }
                    element.addEventListener('focus', e => this.handlerFocusOnLabel(e, form, field));
                    element.addEventListener('blur', e => this.handlerBlurOnLabel(e, form, field));
                    element.addEventListener('input', e => this.handlerInputOnInput(e, form, field, element));
                    element.getAttribute('type') === 'file' ? this.applyStylingFileUpload(element) : null;

                    // устанавливаем "*" в лейбле поля и в плейсхолдере
                    this.setLabelRequireOnField(field, element);
                });

            }

        });
    }

    this.getParameter = function (value) {
        // получаем параметр из кастомных настроек, если там нету, то из значений по умолчанию
        // if (this.checkingCustomSettings() && this.checkingCustomParameter(value)) {
        //     console.log(`"${value}" (Кастомный) = ${settings[value]}`);
        // } else {
        //     console.log(`"${value}" (Дефолтный) = ${SETTINGS[value]}`);
        // }
        return this.checkingCustomSettings() && this.checkingCustomParameter(value) ? settings[value] : SETTINGS[value];
    }
    this.checkingCustomSettings = function () {
        return (typeof settings === 'object' && Object.keys(settings).length > 0);
    }
    this.checkingCustomParameter = function (value) {
        return typeof settings[value] !== 'undefined'
    }
    this.setLabelRequireOnField = function (field, element) {
        // устанавливаем "*" в лейбле поля и в плейсхолдере
        if (element.hasAttribute("data-require") && !this.checkingValueAttributeTypeOnInput(element, 'checkbox')) {
            field.querySelector('.form__label').innerHTML += '<span class="form__require">*</span>';
            if (this.checkingStockAttributePlaceholderOnInput(element)) {
                element.setAttribute('placeholder', element.getAttribute('placeholder') + '*');
            }
        }
    }
    this.checkingStockAttributeRequireOnInput = function (element) {
        return element.hasAttribute('data-require');
    }
    this.checkingStockAttributePlaceholderOnInput = function (element) {
        return element.hasAttribute('placeholder');
    }
    this.checkingValueAttributeTypeOnInput = function (element, type) {
        return element.getAttribute('type') === type;
    }
    this.setBlockedButtonSubmit = function (form) {
        let filled = {};
        form.querySelector('.form__submit').disabled = true;
        form.querySelectorAll('.form__field').forEach(field => {
            const element = field.querySelector(".form__input");
            if (element.hasAttribute("data-require")) {
                // if (element.getAttribute("data-type") === 'agreement') {
                //     filled[element.getAttribute("name")] = true;
                // }
                element.addEventListener('input', e => this.handlerChangeOnRequireField(e, form, filled));
            }
        });
    }
    this.handlerChangeOnRequireField = function (e, form, filled) {
        // console.clear();
        let element = e.target,
            name = e.target.getAttribute('name'),
            type = e.target.getAttribute('data-type');
        switch (type) {
            case 'phone':
                if (e.target.value.match(/\d+/g) && (e.target.value.match( /\d+/g ).join('')).length === 11) {
                    filled[name] = true;
                } else {
                    delete filled[name];
                }
                // if (e.target.value !== '' && e.target.value !== ' (___) ___-__-__' && e.target.value !== '+ (___) ___-__-__' && e.target.value !== '+7(___)___-__-__') {
                //     filled[name] = true;
                // } else if (e.target.value === ' (___) ___-__-__' || e.target.value === '+ (___) ___-__-__' || e.target.value === '+7(___)___-__-__') {
                //     delete filled[name];
                // } else {
                //     delete filled[name];
                // }
                break;
            case 'agreement':
                if (element.checked) {
                    filled[name] = true;
                } else {
                    delete filled[name];
                }
                break;
            default:
                if (e.target.value) {
                    filled[name] = true;
                } else {
                    delete filled[name];
                }
                break;
        }
        if (Object.keys(filled).length === this.getCountRequireField(form)) {
            form.querySelector('.form__submit').disabled = false;
        } else {
            form.querySelector('.form__submit').disabled = true;
        }
    }
    this.getCountRequireField = function (form) {
        return form.querySelectorAll('.form__input[data-require]').length;
    }
    this.validateField = function (form, field, element) {
        // const name = element.getAttribute("name");
        const type = element.getAttribute("data-type");
        switch (type) {
            case 'login':
                if (!formData.login.length) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Поле заполнено некорректно');
                }
                if (!/^[a-zA-Z0-9]+$/.test(formData.login)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Логин может содержать только буквы на латинице и цифры');
                }
                if (formData.login.length < 3) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Логин должен состоять не менее 3 символов');
                }
                break;
            case 'password':
                if (!formData.password.length) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Поле заполнено некорректно');
                }
                if (formData.password.length < 6) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Пароль должен состоять не менее 6 символов');
                }
                if (formData.password.length > 10) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Пароль должен состоять не более 10 символов');
                }
                if (!/^[a-zA-Z0-9]+$/.test(formData.password)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Пароль может содержать только буквы на латинице и цифры');
                }
                if (!/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[a-zA-Z0-9]{6,10}$/.test(formData.password)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Пароль должен содержать как минимум одну заглавную букву, одну строчную букву и одну цифру');
                }
                break;
            case 'passwordConfirm':
                if (!formData.passwordConfirm.length) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Повторите пароль');
                }
                if (formData.password !== formData.passwordConfirm) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Пароли не совпадают');
                }
                break;
            case 'phone':
                const phonePattern = /^((8|\+374|\+994|\+995|\+375|\+7|\+380|\+38|\+996|\+998|\+993)[\- ]?)?\(?\d{3,5}\)?[\- ]?\d{1}[\- ]?\d{1}[\- ]?\d{1}[\- ]?\d{1}[\- ]?\d{1}(([\- ]?\d{1})?[\- ]?\d{1})?$/i;
                const phoneTest = !phonePattern.test(formData.phone);
                const phoneLength = (formData.phone.match( /\d+/g ).join('')).length > 1;

                if ((this.checkingStockAttributeRequireOnInput(element) && phoneTest) || (phoneLength && phoneTest)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, "Поле заполнено некорректно");
                }
                // if (data.rules[type].test(element.value)) {
                //     this.removeMessageOnError(field);
                // } else {
                //     errors = true;
                //     this.addClassOnError(field);
                //     this.addMessageOnError(field, data.messages.fields[type]);
                // }
                break;
            case 'email':
                const emailPattern = /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
                const emailPatternSymbol = /^[a-zA-Z0-9.@-]+$/;
                const emailTest = !emailPattern.test(formData.email);
                const emailTestSymbol = !emailPatternSymbol.test(formData.email);
                const emailLength = formData.email.length;

                if ((this.checkingStockAttributeRequireOnInput(element) && emailTestSymbol) || (emailLength && emailTestSymbol)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, 'Почта может содержать только буквы на латинице, цифры и символы: дефис и точка');
                }
                if ((this.checkingStockAttributeRequireOnInput(element) && emailTest) || (emailLength && emailTest)) {
                    errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, "Поле заполнено некорректно");
                }
                // if (data.rules[type].test(element.value)) {
                //     this.removeMessageOnError(field);
                // } else {
                //     errors = true;
                //     this.addClassOnError(field);
                //     this.addMessageOnError(field, data.messages.fields[type]);
                // }
                break;
            case 'agreement':
                if (!element.checked) {
                    errors = true;
                    this.addClassOnError(field);
                }
                // if (element.checked) {
                //     this.removeMessageOnError(field);
                // } else {
                //     errors = true;
                //     this.addClassOnError(field);
                // }
                break;
            default:
                if (type) {
                    if (!formData[type].length && this.checkingStockAttributeRequireOnInput(element)) {
                        errors = true;
                        this.addClassOnError(field);
                        this.addMessageOnError(field, "Поле заполнено некорректно");
                    }
                }
                // if (element.value === '') {
                //     errors = true;
                //     this.addClassOnError(field);
                //     this.addMessageOnError(field, data.messages.default);
                // } else {
                //     this.removeMessageOnError(field);
                // }
                break;
        }
    }
    this.serializeForm = function (form) {
        formData = {};
        try {
            new FormData(form).forEach((value, key) => {
                formData[key] = value.trim();
            });
            // this.fetchToSend();
        } catch (error) {
            console.log("Error:", error);
        }
    }
    this.validateForm = function (form, e) {
        this.serializeForm(form);
        errors = false;
        let warningElems = form.querySelectorAll(".form__field.is-error") || true;
        if (warningElems.length) {
            warningElems.forEach(function (warningElem) {
                return warningElem.classList.remove("is-error");
            });
        }
        form.querySelectorAll('.form__field').forEach(field => {
            let element = field.querySelector("input, textarea, select");
            // if (element.hasAttribute("data-require")) {
            this.validateField(form, field, element);
            // }
        });
        if (errors) {
            e.preventDefault();
        } else {
            e.preventDefault();
            // console.log(this.getParameter('captcha'))
            // this.sendingFormData(form);
            if (this.getParameter('captcha')) {
                // когда параметр 'captcha' = true, то делаем получение токена ти устанавливаем токен в скрытый инпут
                e.preventDefault();
                this.getCaptchaToken(form);
            } else {
                form.submit();
            }
        }
    }
    this.handlerOnSubmitForm = function (e, form) {
        this.validateForm(form, e);
    }
    this.addClassOnError = function (field) {
        field.classList.add("is-error");
    }
    this.removeClassOnError = function (field) {
        field.classList.remove("is-error");
    }
    this.addMessageOnError = function (field, message) {
        if (!field.querySelector('.form__error-message')) {
            field.insertAdjacentHTML('beforeend', `<div class="form__error-message">${message}</div>`);
        }
    }
    this.removeMessageOnError = function (field) {
        if (field.querySelector('.form__error-message')) {
            field.querySelector('.form__error-message').remove();
        }
    }
    this.addMaskPhone = function (element) {
        new IMask(element, {
            mask: "+{7}(000)000-00-00",
            lazy: false,
        });
        // new IMask(element, {
        //     mask: [
        //         {
        //             mask: '+{0} (000) 000-00-00',
        //             startsWith: '7',
        //             lazy: false,
        //             country: 'Russia'
        //         },
        //         {
        //             mask: '{0} (000) 000-00-00',
        //             startsWith: '8',
        //             lazy: false,
        //             country: 'Russia'
        //         },
        //     ],
        //     dispatch: (appended, dynamicMasked) => {
        //         const number = (dynamicMasked.value + appended).replace(/\D/g, '');
        //         return dynamicMasked.compiledMasks.find(m => number.indexOf(m.startsWith) === 0);
        //     }
        // })
    }
    this.handlerFocusOnLabel = function (e, form, field) {
        if (field.querySelector('.form__label') && field.querySelector('.form__input').getAttribute('type') !== 'checkbox') {
            field.classList.add('is-focused');
            if (form.classList.contains('form-with-hidding-label')) {
                field.querySelector('.form__label').classList.add('is-hidden');
            }
        }
    }
    this.handlerBlurOnLabel = function (e, form, field) {
        if (field.querySelector('.form__label') && e.currentTarget.value === '') {
            field.querySelector('.form__label').classList.remove('is-hidden');
            field.classList.remove('is-focused');
        }
    }
    this.handlerInputOnInput = function (e, form, field, element) {
        this.removeClassOnError(field);
        this.removeMessageOnError(field);
    }
    this.applyStylingFileUpload = function (element) {
        $(element).simpleFileInput({
            placeholder: 'Прикрепить файл',
            buttonText: '',
            width: 'false',
        });
    }
    this.getCaptchaToken = function (form) {
        grecaptcha.ready(() => {
            grecaptcha.execute(this.getParameter('captchaKey'), {
                action: 'add_form'
            })
                .then(token => {
                    this.setCaptchaToken(form, token);
                });
        });
    }
    this.setCaptchaToken = function (form, token) {
        form.querySelector(`.${this.getParameter('captchaInputClass')}`).value = token;
        this.serializeForm(form);
        this.sendingFormData(form);
    }
    this.sendingFormData = function (form) {
        fetch(this.getParameter('urlSendingFormData'), {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            body: new URLSearchParams(formData).toString()
        })
            .then(response => {
                if (!response.ok) {
                    throw new Error('Ошибка запроса');
                }
                return response.json();
            })
            .then(data => {
                // console.log(data);
                if (data.success) {
                    // console.log('Заявка успешно отправлена');
                    this.openPopupSuccessfulSending();
                } else {
                    this.openPopupErrorSending();
                }
            })
            .catch(error => {
                this.openPopupErrorSending();
                console.log(error);
            });

    }
    this.openPopupSuccessfulSending = function (form) {
        document.body.querySelector(`.${this.getParameter('classPopupSuccessful')}`).classList.add(this.getParameter('classActiveOnPopup'));
    }
    this.openPopupErrorSending = function (form) {
        document.body.querySelector(`.${this.getParameter('classPopupError')}`).classList.add(this.getParameter('classActiveOnPopup'));
    }

    if (FORM) {
        this.init();
    }
}
    
ver.2.0.0
JS
Как юзать
      new Form({
    blockedButtonSubmit: false
});
    
Код
      const Form = function (settings) {
    const FORM = document.querySelectorAll('form');

    let data = {
        errors: false,
        rules: {
            phone: /^((8|\+374|\+994|\+995|\+375|\+7|\+380|\+38|\+996|\+998|\+993)[\- ]?)?\(?\d{3,5}\)?[\- ]?\d{1}[\- ]?\d{1}[\- ]?\d{1}[\- ]?\d{1}[\- ]?\d{1}(([\- ]?\d{1})?[\- ]?\d{1})?$/i,
            email: /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i,
        },
        messages: {
            default: "Поле заполнено некорректно",
            fields: {
                phone: "Поле заполнено некорректно",
                email: "Поле заполнено некорректно",
            },
        },
    }

    let formData = {};

    this.init = function () {
        FORM.forEach(form => {
            form.querySelector('[data-type="phone"]') ? this.addMaskPhone(form.querySelector('[data-type="phone"]')) : null;

            // если включен режим блокировки кнопки отправки
            if (settings.blockedButtonSubmit) {
                this.setBlockedButtonSubmit(form);
            } else {
                form.addEventListener('submit', e => this.handlerOnSubmitForm(e, form));
            }

            form.querySelectorAll('.form__field').forEach(field => {
                let element = field.querySelector(".form__input");
                if (element.value !== '' && element.getAttribute('type') !== 'checkbox') {
                    if (form.classList.contains('form-with-hidding-label')) {
                        field.querySelector('.form__label').classList.add('is-hidden');
                    } else {
                        field.classList.add('is-focused');
                    }
                }
                element.addEventListener('focus', e => this.handlerFocusOnLabel(e, form, field));
                element.addEventListener('blur', e => this.handlerBlurOnLabel(e, form, field));
                element.getAttribute('type') === 'file' ? this.applyStylingFileUpload(element) : null;
            });

        });
    }
    this.setBlockedButtonSubmit = function (form) {
        let filled = {};
        form.querySelector('.form__submit').disabled = true;
        form.querySelectorAll('.form__field').forEach(field => {
            let element = field.querySelector(".form__input");
            if (element.getAttribute("data-require")) {
                if (element.getAttribute("data-type") === 'agreement') {
                    filled[element.getAttribute("name")] = true;
                }
                element.addEventListener('input', e => this.handlerChangeOnRequireField(e, form, filled));
            }
        });
    }
    this.getCountRequireField = function (form) {
        return form.querySelectorAll('.form__input[data-require]').length;
    }
    this.handlerChangeOnRequireField = function (e, form, filled) {
        let element = e.target,
            name = e.target.getAttribute('name'),
            type = e.target.getAttribute('data-type');
        switch (type) {
            case 'phone':
                if (e.target.value !== '' && e.target.value !== ' (___) ___-__-__' && e.target.value !== '+ (___) ___-__-__') {
                    filled[name] = true;
                } else if (e.target.value === ' (___) ___-__-__' || e.target.value === '+ (___) ___-__-__') {
                    delete filled[name];
                } else {
                    delete filled[name];
                }
                break;
            case 'agreement':
                if (element.checked) {
                    filled[name] = true;
                } else {
                    delete filled[name];
                }
                break;
            default:
                if (e.target.value) {
                    filled[name] = true;
                } else {
                    delete filled[name];
                }
                break;
        }
        if (Object.keys(filled).length === this.getCountRequireField(form)) {
            form.querySelector('.form__submit').disabled = false;
        } else {
            form.querySelector('.form__submit').disabled = true;
        }
    }
    this.validateField = function (form, field, element) {
        let type = element.getAttribute("data-type");

        switch (type) {
            case 'phone':
                if (data.rules[type].test(element.value)) {
                    this.removeMessageOnError(field);
                } else {
                    data.errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, data.messages.fields[type]);
                }
                break;
            case 'email':
                if (data.rules[type].test(element.value)) {
                    this.removeMessageOnError(field);
                } else {
                    data.errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, data.messages.fields[type]);
                }
                break;
            case 'agreement':
                if (form.querySelector('.form__field-agreement .form__input').checked) {
                    this.removeMessageOnError(field);
                } else {
                    data.errors = true;
                    this.addClassOnError(field);
                }
                break;
            default:
                if (element.value === '') {
                    data.errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, data.messages.default);
                } else {
                    this.removeMessageOnError(field);
                }
                break;
        }
    }

    this.serializeForm = function (form) {
        try {
            new FormData(form).forEach((value, key) => {
                formData[key] = value.trim();
            });
            this.fetchToSend();
        } catch (error) {
            console.log("Error:", error);
        }
    }
    this.fetchToSend = function () {
        document.querySelector('.popup-sending').classList.add('is-opened');
        let url = '';
        let params = {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            body: new URLSearchParams(formData),
        };
        const response = fetch(url, params);
        if (response.ok) {
            const data = response.json();
            if (data.success) {
                // console.log('Заявка успешно отправлена');
                document.querySelector('.popup-sending').classList.remove('is-opened');
                document.querySelector('.popup-sending-is-successful').classList.add('is-opened');
            }
        } else {
            console.log('При отправке данных произошла ошибка');
        }
    }
    this.validateForm = function (form, e) {
        data.errors = false;
        let warningElems = form.querySelectorAll(".form__field.is-error") || true;
        if (warningElems.length) {
            warningElems.forEach(function (warningElem) {
                return warningElem.classList.remove("is-error");
            });
        }
        form.querySelectorAll('.form__field').forEach(field => {
            let element = field.querySelector("input, textarea, select");
            if (element.getAttribute("data-require")) {
                this.validateField(form, field, element);
            }
        });
        if (data.errors) {
            e.preventDefault();
        } else {
            this.serializeForm(form);
            form.submit();
        }
    }
    this.handlerOnSubmitForm = function (e, form) {
        this.validateForm(form, e);
    }
    this.addClassOnError = function (field) {
        field.classList.add("is-error");
    }
    this.addMessageOnError = function (field, message) {
        if (!field.querySelector('.form__error-message')) {
            field.insertAdjacentHTML('beforeend', `
${message}
`);
        }
    }
    this.removeMessageOnError = function (field) {
        if (field.querySelector('.form__error-message')) {
            field.querySelector('.form__error-message').remove();
        }
    }
    this.addMaskPhone = function (element) {
        new IMask(element, {
            mask: [
                {
                    mask: '+{0} (000) 000-00-00',
                    startsWith: '7',
                    lazy: false,
                    country: 'Russia'
                },
                {
                    mask: '{0} (000) 000-00-00',
                    startsWith: '8',
                    lazy: false,
                    country: 'Russia'
                },
            ],
            dispatch: (appended, dynamicMasked) => {
                const number = (dynamicMasked.value + appended).replace(/\D/g, '');
                return dynamicMasked.compiledMasks.find(m => number.indexOf(m.startsWith) === 0);
            }
        })
    }
    this.handlerFocusOnLabel = function (e, form, field) {
        if (field.querySelector('.form__label') && field.querySelector('.form__input').getAttribute('type') !== 'checkbox') {
            field.classList.add('is-focused');
            if (form.classList.contains('form-with-hidding-label')) {
                field.querySelector('.form__label').classList.add('is-hidden');
            }
        }
    }
    this.handlerBlurOnLabel = function (e, form, field) {
        if (field.querySelector('.form__label') && e.currentTarget.value === '') {
            field.querySelector('.form__label').classList.remove('is-hidden');
            field.classList.remove('is-focused');
        }
    }
    this.applyStylingFileUpload = function (element) {
        $(element).simpleFileInput({
            placeholder: 'Прикрепить файл',
            buttonText: '',
            width: 'false',
        });
    }

    FORM ? this.init() : null;
}
    
ver.1.0.0
JS
Как юзать
      new Form({
    blockedButtonSubmit: false
});
    
Код
      const Form = function (settings) {
    const FORM = document.querySelectorAll('form');

    let data = {
        errors: false,
        rules: {
            phone: /^((8|\+374|\+994|\+995|\+375|\+7|\+380|\+38|\+996|\+998|\+993)[\- ]?)?\(?\d{3,5}\)?[\- ]?\d{1}[\- ]?\d{1}[\- ]?\d{1}[\- ]?\d{1}[\- ]?\d{1}(([\- ]?\d{1})?[\- ]?\d{1})?$/i,
            email: /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i,
        },
        messages: {
            default: "Поле заполнено некорректно",
            fields: {
                phone: "Поле заполнено некорректно",
                email: "Поле заполнено некорректно",
            },
        },
    }

    this.init = function () {
        FORM.forEach(form => {
            form.querySelector('[data-type="phone"]') ? this.addMaskPhone(form.querySelector('[data-type="phone"]')) : null;

            // если включен режим блокировки кнопки отправки
            if (settings.blockedButtonSubmit) {
                this.setBlockedButtonSubmit(form);
            } else {
                form.addEventListener('submit', e => this.handlerOnSubmitForm(e, form));
            }

            form.querySelectorAll('.form__field').forEach(field => {
                let element = field.querySelector(".form__input");
                if (element.value !== '' && element.getAttribute('type') !== 'checkbox') {
                    if (form.classList.contains('form-with-hidding-label')) {
                        field.querySelector('.form__label').classList.add('is-hidden');
                    } else {
                        field.classList.add('is-focused');
                    }
                }
                element.addEventListener('focus', e => this.handlerFocusOnLabel(e, form, field));
                element.addEventListener('blur', e => this.handlerBlurOnLabel(e, form, field));
            });

        });
    }
    this.setBlockedButtonSubmit = function (form) {
        let filled = {};
        form.querySelector('.form__submit').disabled = true;
        form.querySelectorAll('.form__field').forEach(field => {
            let element = field.querySelector(".form__input");
            if (element.getAttribute("data-require")) {
                if (element.getAttribute("data-type") === 'agreement') {
                    filled[element.getAttribute("name")] = true;
                }
                element.addEventListener('input', e => this.handlerChangeOnRequireField(e, form, filled));
            }
        });
    }
    this.getCountRequireField = function (form) {
        return form.querySelectorAll('.form__input[data-require]').length;
    }
    this.handlerChangeOnRequireField = function (e, form, filled) {
        let element = e.target,
            name = e.target.getAttribute('name'),
            type = e.target.getAttribute('data-type');
        switch (type) {
            case 'phone':
                if (e.target.value !== '' && e.target.value !== ' (___) ___-__-__' && e.target.value !== '+ (___) ___-__-__') {
                    filled[name] = true;
                } else if (e.target.value === ' (___) ___-__-__' || e.target.value === '+ (___) ___-__-__') {
                    delete filled[name];
                } else {
                    delete filled[name];
                }
                break;
            case 'agreement':
                if (element.checked) {
                    filled[name] = true;
                } else {
                    delete filled[name];
                }
                break;
            default:
                if (e.target.value) {
                    filled[name] = true;
                } else {
                    delete filled[name];
                }
                break;
        }
        if (Object.keys(filled).length === this.getCountRequireField(form)) {
            form.querySelector('.form__submit').disabled = false;
        } else {
            form.querySelector('.form__submit').disabled = true;
        }
    }
    this.validateField = function (form, field, element) {
        let type = element.getAttribute("data-type");

        switch (type) {
            case 'phone':
                if (data.rules[type].test(element.value)) {
                    this.removeMessageOnError(field);
                } else {
                    data.errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, data.messages.fields[type]);
                }
                break;
            case 'email':
                if (data.rules[type].test(element.value)) {
                    this.removeMessageOnError(field);
                } else {
                    data.errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, data.messages.fields[type]);
                }
                break;
            case 'agreement':
                if (form.querySelector('.form__field-agreement .form__input').checked) {
                    this.removeMessageOnError(field);
                } else {
                    data.errors = true;
                    this.addClassOnError(field);
                }
                break;
            default:
                if (element.value === '') {
                    data.errors = true;
                    this.addClassOnError(field);
                    this.addMessageOnError(field, data.messages.default);
                } else {
                    this.removeMessageOnError(field);
                }
                break;
        }
    }
    this.validateForm = function (form, e) {
        data.errors = false;
        let warningElems = form.querySelectorAll(".form__field.is-error") || true;
        if (warningElems.length) {
            warningElems.forEach(function (warningElem) {
                return warningElem.classList.remove("is-error");
            });
        }
        form.querySelectorAll('.form__field').forEach(field => {
            let element = field.querySelector("input, textarea, select");
            if (element.getAttribute("data-require")) {
                this.validateField(form, field, element);
            }
        });
        if (data.errors) {
            e.preventDefault();
        } else {
            form.submit();
        }
    }
    this.handlerOnSubmitForm = function (e, form) {
        this.validateForm(form, e);
    }
    this.addClassOnError = function (field) {
        field.classList.add("is-error");
    }
    this.addMessageOnError = function (field, message) {
        if (!field.querySelector('.form__error-message')) {
            field.insertAdjacentHTML('beforeend', `<div class="form__error-message">${message}</div>`);
        }
    }
    this.removeMessageOnError = function (field) {
        if (field.querySelector('.form__error-message')) {
            field.querySelector('.form__error-message').remove();
        }
    }
    this.addMaskPhone = function (element) {
        new IMask(element, {
            mask: [
                {
                    mask: '+{0} (000) 000-00-00',
                    startsWith: '7',
                    lazy: false,
                    country: 'Russia'
                },
                {
                    mask: '{0} (000) 000-00-00',
                    startsWith: '8',
                    lazy: false,
                    country: 'Russia'
                },
            ],
            dispatch: (appended, dynamicMasked) => {
                const number = (dynamicMasked.value + appended).replace(/\D/g, '');
                return dynamicMasked.compiledMasks.find(m => number.indexOf(m.startsWith) === 0);
            }
        })
    }
    this.handlerFocusOnLabel = function (e, form, field) {
        if (field.querySelector('.form__label') && field.querySelector('.form__input').getAttribute('type') !== 'checkbox') {
            field.classList.add('is-focused');
            if (form.classList.contains('form-with-hidding-label')) {
                field.querySelector('.form__label').classList.add('is-hidden');
            }
        }
    }
    this.handlerBlurOnLabel = function (e, form, field) {
        if (field.querySelector('.form__label') && e.currentTarget.value === '') {
            field.querySelector('.form__label').classList.remove('is-hidden');
            field.classList.remove('is-focused');
        }
    }

    FORM ? this.init() : null;
}