Интеграция с HTML5 валидацией

HTML5 предоставляет встроенную систему проверки форм: обязательные поля, ограничения длины, регулярные выражения, типы данных и пользовательские сообщения об ошибках. При интеграции с Tom Select возникает важная особенность: библиотека скрывает оригинальный <select> или <input> и создаёт собственный интерфейс поверх стандартного элемента. Из-за этого поведение нативной валидации начинает отличаться от обычных HTML-форм.

Понимание того, как Tom Select взаимодействует с HTML5 Validation API, позволяет избежать проблем с отправкой форм, отображением ошибок и синхронизацией состояния элементов.


Как работает HTML5-валидация

HTML5-валидация основывается на свойствах и атрибутах DOM-элементов:

Атрибут Назначение
required Поле обязательно
pattern Проверка по регулярному выражению
minlength / maxlength Ограничение длины
type="email" Проверка email
multiple Разрешение нескольких значений

Методы Validation API:

element.checkValidity()

Проверяет поле и возвращает true или false.

element.reportValidity()

Показывает встроенное сообщение браузера.

element.setCustomValidity(message)

Устанавливает собственную ошибку.


Базовая интеграция Tom Select с required

Стандартный <select>:

<form id="form">
    <select id="country" required>
        <option value="">Выберите страну</option>
        <option value="kz">Казахстан</option>
        <option value="ru">Россия</option>
    </select>

    <button type="submit">Отправить</button>
</form>

Подключение Tom Select:

new TomSelect('#country');

После инициализации библиотека скрывает исходный элемент:

<select hidden tabindex="-1">

Однако оригинальный <select> остаётся частью формы, поэтому HTML5-валидация продолжает работать.


Проблема с отображением ошибки

Хотя браузер валидирует скрытый <select>, визуальное сообщение может отображаться некорректно:

  • фокус не переводится на Tom Select;
  • браузер пытается подсветить скрытый элемент;
  • popup ошибки отображается в неожиданной позиции.

Особенно часто это происходит в:

  • Safari;
  • Firefox;
  • мобильных браузерах.

Правильная обработка required

Наиболее стабильный подход — валидировать форму вручную.

const form = document.querySelector('#form');

form.addEventListener('submit', (e) => {
    if (!form.checkValidity()) {
        e.preventDefault();
        form.reportValidity();
    }
});

Tom Select продолжит синхронизировать значение с оригинальным <select>.


Валидация multiple select

HTML:

<select id="skills" multiple required>
    <option value="js">JavaScript</option>
    <option value="ts">TypeScript</option>
    <option value="vue">Vue</option>
</select>

Tom Select:

new TomSelect('#skills');

HTML5 считает поле валидным только тогда, когда выбран минимум один элемент.

Проверка:

const select = document.querySelector('#skills');

console.log(select.checkValidity());

Если список пуст:

false

Проверка минимального количества выбранных элементов

HTML5 не умеет ограничивать минимальное число выбранных значений в <select multiple>.

Для этого используется пользовательская валидация.

const select = document.querySelector('#skills');

select.addEventListener('change', () => {

    if (select.selectedOptions.length < 2) {
        select.setCustomValidity('Выберите минимум 2 навыка');
    } else {
        select.setCustomValidity('');
    }

});

Интеграция через события Tom Select

Tom Select предоставляет собственные события:

const control = new TomSelect('#skills');

control.on('change', () => {

    const select = control.input;

    if (select.selectedOptions.length < 2) {
        select.setCustomValidity('Минимум 2 значения');
    } else {
        select.setCustomValidity('');
    }

});

control.input — ссылка на оригинальный DOM-элемент.


Использование reportValidity

Для немедленного показа ошибки:

control.on('change', () => {

    const select = control.input;

    if (select.selectedOptions.length < 2) {

        select.setCustomValidity('Недостаточно элементов');

        select.reportValidity();

    } else {

        select.setCustomValidity('');

    }

});

Валидация input-режима

Tom Select может работать поверх <input>.

<input id="email" type="email" required>
new TomSelect('#email', {
    create: true
});

В этом случае HTML5-валидация типа email продолжает работать:

document.querySelector('#email').checkValidity();

Проблема create:true и email validation

При использовании:

create: true

пользователь может вводить произвольные значения.

Если поле имеет:

type="email"

браузер проверит строку только при отправке формы.

Пример:

<input id="users" type="email" multiple>

Tom Select:

new TomSelect('#users', {
    create: true
});

Допустимый ввод:

user@example.com

Недопустимый:

invalid-email

Мгновенная email-валидация

Для проверки во время ввода:

const emailInput = document.querySelector('#users');

const control = new TomSelect('#users', {
    create: true
});

control.on('item_add', () => {

    if (!emailInput.checkValidity()) {

        emailInput.setCustomValidity('Некорректный email');

        emailInput.reportValidity();

    } else {

        emailInput.setCustomValidity('');

    }

});

Работа с pattern

HTML:

<input
    id="username"
    pattern="[a-zA-Z0-9_]+"
    required
>

Tom Select:

new TomSelect('#username', {
    create: true
});

Теперь браузер автоматически проверяет:

  • латиницу;
  • цифры;
  • символ _.

Пользовательская проверка pattern

Иногда требуется немедленная валидация:

const input = document.querySelector('#username');

const regex = /^[a-zA-Z0-9_]+$/;

const control = new TomSelect('#username', {
    create: true
});

control.on('item_add', (value) => {

    if (!regex.test(value)) {

        input.setCustomValidity(
            'Разрешены только буквы, цифры и _'
        );

        input.reportValidity();

    } else {

        input.setCustomValidity('');

    }

});

Стилизация invalid-состояния

HTML5 использует псевдокласс:

:invalid

Однако Tom Select визуально отображает собственный контейнер, а не оригинальный элемент.

Поэтому:

select:invalid

не влияет на UI библиотеки.


Подсветка Tom Select при ошибке

Использование класса:

const control = new TomSelect('#country');

const select = control.input;

select.addEventListener('invalid', () => {

    control.wrapper.classList.add('invalid');

});

CSS:

.ts-wrapper.invalid {
    border: 1px solid red;
}

Удаление ошибки:

control.on('change', () => {

    if (select.checkValidity()) {
        control.wrapper.classList.remove('invalid');
    }

});

Интеграция Bootstrap validation

Bootstrap использует классы:

.is-valid
.is-invalid

Интеграция:

const control = new TomSelect('#country');

const select = control.input;

select.addEventListener('invalid', () => {

    control.wrapper.classList.add('is-invalid');

});

Очистка:

control.on('change', () => {

    if (select.checkValidity()) {

        control.wrapper.classList.remove('is-invalid');
        control.wrapper.classList.add('is-valid');

    }

});

Интеграция с Constraint Validation API

Validation API позволяет создавать сложные правила.

Пример ограничения:

const control = new TomSelect('#tags', {
    create: true
});

const select = control.input;

control.on('change', () => {

    const values = control.items;

    const forbidden = values.includes('admin');

    if (forbidden) {

        select.setCustomValidity(
            'Тег admin запрещён'
        );

    } else {

        select.setCustomValidity('');

    }

});

Проверка уникальности значений

Tom Select сам умеет запрещать дубли:

duplicates: false

Но иногда требуется дополнительная логика:

control.on('item_add', () => {

    const values = control.items;

    const unique = new Set(values);

    if (unique.size !== values.length) {

        select.setCustomValidity(
            'Обнаружены дубликаты'
        );

    } else {

        select.setCustomValidity('');

    }

});

Асинхронная серверная валидация

Проверка доступности имени:

const control = new TomSelect('#login', {
    create: true
});

const input = control.input;

control.on('change', async (value) => {

    const response = await fetch(`/check-login?value=${value}`);

    const result = await response.json();

    if (!result.available) {

        input.setCustomValidity(
            'Логин уже занят'
        );

        input.reportValidity();

    } else {

        input.setCustomValidity('');

    }

});

Предотвращение отправки формы

Если поле невалидно:

form.addEventListener('submit', (e) => {

    if (!form.checkValidity()) {

        e.preventDefault();

        form.reportValidity();

    }

});

Это работает корректно даже с несколькими экземплярами Tom Select.


Валидация при blur

Проверка после потери фокуса:

control.control_input.addEventListener('blur', () => {

    if (!select.checkValidity()) {

        control.wrapper.classList.add('invalid');

    }

});

control.control_input — внутреннее текстовое поле Tom Select.


Очистка ошибок при вводе

Автоматическое снятие invalid-состояния:

control.on('type', () => {

    select.setCustomValidity('');

    control.wrapper.classList.remove('invalid');

});

Работа с disabled

HTML5 игнорирует отключённые поля:

<select disabled required>

Tom Select синхронизирует это состояние автоматически:

control.disable();

Повторное включение:

control.enable();

Проверка maxlength

HTML:

<input
    id="tags"
    maxlength="20"
>

Tom Select:

new TomSelect('#tags', {
    create: true
});

HTML5 проверяет длину значения, но при multiple-режиме часто требуется ограничение количества элементов, а не длины строки.


Ограничение количества элементов

Tom Select предоставляет встроенную настройку:

maxItems: 3

Пример:

new TomSelect('#skills', {
    maxItems: 3
});

Это ограничение работает независимо от HTML5 Validation API.


Комбинирование встроенной и пользовательской валидации

Наиболее стабильный подход:

  1. Использовать HTML5-атрибуты:

    • required
    • pattern
    • type
    • maxlength
  2. Использовать Tom Select для UI.

  3. Добавлять setCustomValidity() для сложной логики.

  4. Валидировать форму через:

    form.checkValidity()

Полноценный пример интеграции

HTML:

<form id="user-form">

    <select
        id="skills"
        multiple
        required
    >
        <option value="js">JavaScript</option>
        <option value="ts">TypeScript</option>
        <option value="vue">Vue</option>
        <option value="react">React</option>
    </select>

    <button type="submit">
        Отправить
    </button>

</form>

Jav * aScript:

const form = document.querySelector('#user-form');

const control = new TomSelect('#skills', {
    maxItems: 5
});

const select = control.input;

control.on('change', () => {

    if (control.items.length < 2) {

        select.setCustomValidity(
            'Выберите минимум 2 технологии'
        );

        control.wrapper.classList.add('invalid');

    } else {

        select.setCustomValidity('');

        control.wrapper.classList.remove('invalid');

    }

});

form.addEventListener('submit', (e) => {

    if (!form.checkValidity()) {

        e.preventDefault();

        form.reportValidity();

    }

});

CSS:

.ts-wrapper.invalid {
    border: 1px solid #d00;
}

Типичные проблемы интеграции

Браузер показывает ошибку не рядом с полем

Причина:

  • оригинальный элемент скрыт;
  • popup позиционируется относительно hidden-элемента.

Решение:

  • собственный UI ошибок;
  • setCustomValidity();
  • кастомные tooltip.

invalid не срабатывает

Причина:

  • событие работает только при отправке формы;
  • checkValidity() отдельно не вызывает invalid UI.

Решение:

reportValidity()

required не работает

Причина:

  • отсутствует пустой <option value="">;
  • select уже содержит значение.

Правильно:

<option value=""></option>

Валидация срабатывает до инициализации

Если форма валидируется раньше создания Tom Select:

form.checkValidity();

то состояние UI ещё не синхронизировано.

Правильный порядок:

const control = new TomSelect(...);

form.checkValidity();

Рекомендации по архитектуре

Использование оригинального элемента как единственного источника истины

Правильный подход:

control.input

Неправильный:

control.items

для основной валидации формы.

HTML5 Validation API работает только с DOM-элементом.


Централизация логики проверки

Хорошая практика:

function validateSkills(control) {

    const select = control.input;

    if (control.items.length < 2) {

        select.setCustomValidity(
            'Минимум 2 значения'
        );

    } else {

        select.setCustomValidity('');

    }

}

Изоляция UI-ошибок

Лучше разделять:

  • логику валидации;
  • отображение ошибок;
  • управление CSS-классами.

Пример:

function showError(control) {
    control.wrapper.classList.add('invalid');
}

function clearError(control) {
    control.wrapper.classList.remove('invalid');
}

Совместимость с браузерами

Особенности HTML5-валидации и Tom Select:

Браузер Особенности
Chrome Наиболее стабильная работа
Firefox Возможны проблемы с popup ошибки
Safari Часто некорректный фокус
Mobile Safari Нестабильное позиционирование tooltip
Edge Поведение близко к Chrome

Для сложных интерфейсов часто используется полностью кастомная система ошибок вместо встроенных browser tooltips.