Пользовательские правила валидации

Стандартных механизмов проверки данных в Tom Select часто оказывается недостаточно для сложных интерфейсов. Ограничение длины строки, запрет пустых значений или базовая фильтрация символов покрывают лишь простые сценарии. В реальных приложениях требуется:

  • проверка уникальности;
  • контроль формата данных;
  • зависимость выбора от других полей;
  • ограничения бизнес-логики;
  • асинхронная серверная проверка;
  • валидация создаваемых пользователем элементов;
  • динамические условия допуска значений.

Пользовательские правила позволяют внедрять собственную логику проверки практически на любом этапе работы компонента.


Базовая архитектура валидации в Tom Select

Проверка значений обычно строится вокруг следующих механизмов:

Механизм Назначение
createFilter Проверка новых значений перед созданием
onItemAdd Контроль добавленных элементов
onChange Общая проверка состояния
onType Анализ вводимого текста
addOption() Валидация добавляемых данных
load() Проверка данных сервера
render Отображение ошибок
DOM API Интеграция с HTML5-валидацией

Проверка создаваемых значений через createFilter

Параметр createFilter используется для разрешения или запрета создания новых элементов.

Простейшая проверка

<select id="tags" multiple></select>
new TomSelect('#tags', {
    create: true,

    createFilter: function(input) {
        return input.length >= 3;
    }
});

Если строка содержит менее трёх символов — новый элемент создан не будет.


Проверка через регулярные выражения

Частый сценарий — контроль формата значения.

Разрешение только латиницы

new TomSelect('#tags', {
    create: true,

    createFilter: function(input) {
        return /^[a-zA-Z]+$/.test(input);
    }
});

Допустимы только английские буквы.


Проверка email

new TomSelect('#emails', {
    create: true,

    createFilter: function(input) {
        return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input);
    }
});

Проверка телефонного номера

new TomSelect('#phones', {
    create: true,

    createFilter: function(input) {
        return /^\+?[0-9]{10,15}$/.test(input);
    }
});

Запрет дубликатов по собственным правилам

Встроенная защита от дубликатов работает не всегда так, как требуется бизнес-логике.

Например, необходимо игнорировать регистр символов.

Проверка без учёта регистра

new TomSelect('#skills', {
    create: true,

    createFilter: function(input) {

        const normalized = input.toLowerCase();

        return !this.options.hasOwnProperty(normalized);
    },

    create: function(input) {
        return {
            value: input.toLowerCase(),
            text: input
        };
    }
});

Проверка запрещённых слов

Иногда требуется исключить определённые значения.

Чёрный список

const banned = ['admin', 'root', 'system'];

new TomSelect('#users', {
    create: true,

    createFilter: function(input) {
        return !banned.includes(input.toLowerCase());
    }
});

Сложная бизнес-валидация

Проверка может включать несколько условий одновременно.

Комбинированные правила

new TomSelect('#codes', {
    create: true,

    createFilter: function(input) {

        if (input.length < 5) {
            return false;
        }

        if (!/[A-Z]/.test(input)) {
            return false;
        }

        if (!/[0-9]/.test(input)) {
            return false;
        }

        return true;
    }
});

Условия:

  • минимум 5 символов;
  • наличие заглавной буквы;
  • наличие цифры.

Валидация через callback create

Метод create позволяет полностью контролировать создание элемента.

Отмена создания через callback

new TomSelect('#tags', {
    create: function(input, callback) {

        if (input.length < 3) {
            callback();
            return;
        }

        callback({
            value: input,
            text: input
        });
    }
});

Если callback() вызывается без объекта — создание отменяется.


Асинхронная серверная проверка

Один из важнейших сценариев — проверка на сервере.

Проверка уникальности username

new TomSelect('#username', {

    create: function(input, callback) {

        fetch('/api/check-user?name=' + encodeURIComponent(input))
            .then(response => response.json())
            .then(data => {

                if (!data.allowed) {
                    callback();
                    return;
                }

                callback({
                    value: input,
                    text: input
                });
            });
    }
});

Блокировка значений после добавления

Иногда значение нужно сначала добавить, а затем проверить общий набор данных.

Ограничение количества специальных тегов

const select = new TomSelect('#tags', {

    onItemAdd: function(value) {

        const special = this.items.filter(item => {
            return item.startsWith('special-');
        });

        if (special.length > 2) {

            this.removeItem(value);

            alert('Максимум два специальных тега');
        }
    }
});

Проверка всего списка значений

Валидация может выполняться после любого изменения.

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

const select = new TomSelect('#skills', {

    onChange: function(values) {

        if (values.length < 2) {
            console.log('Недостаточно значений');
        }
    }
});

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

Tom Select работает поверх обычного элемента формы, поэтому возможна интеграция с браузерной системой проверки.

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

<select id="country" required>
    <option value="">Выберите страну</option>
    <option value="kz">Казахстан</option>
</select>
new TomSelect('#country');

Установка пользовательских ошибок

Можно использовать API браузера.

setCustomValidity

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

const control = new TomSelect(selectElement, {

    onChange: function(values) {

        if (values.length < 3) {

            selectElement.setCustomValidity(
                'Необходимо выбрать минимум 3 значения'
            );

        } else {

            selectElement.setCustomValidity('');
        }
    }
});

Проверка данных перед отправкой формы

Иногда удобнее выполнять финальную проверку непосредственно перед submit.

Полный контроль формы

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

form.addEventListener('submit', function(event) {

    const control = form.tomselect;

    if (control.items.length === 0) {

        event.preventDefault();

        alert('Необходимо выбрать значение');
    }
});

Проверка вводимого текста в реальном времени

Событие onType позволяет анализировать ввод пользователя до создания значения.

Ограничение специальных символов

new TomSelect('#search', {

    onType: function(str) {

        if (/[^a-zA-Z0-9]/.test(str)) {
            console.log('Недопустимые символы');
        }
    }
});

Автоматическая очистка некорректных данных

Иногда удобнее исправлять ввод автоматически.

Удаление пробелов

new TomSelect('#tags', {

    create: function(input) {

        input = input.trim();

        return {
            value: input,
            text: input
        };
    }
});

Нормализация данных перед проверкой

Перед валидацией данные часто приводятся к единому формату.

Преобразование регистра

new TomSelect('#codes', {

    create: function(input) {

        const normalized = input
            .trim()
            .toUpperCase();

        return {
            value: normalized,
            text: normalized
        };
    }
});

Проверка длины списка

Можно ограничивать не только отдельное значение, но и весь набор.

Максимальное количество элементов

new TomSelect('#skills', {

    maxItems: 5,

    onItemAdd: function() {

        if (this.items.length >= 5) {
            console.log('Достигнут лимит');
        }
    }
});

Условная валидация

Правила могут зависеть от состояния интерфейса.

Проверка по выбранной категории

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

new TomSelect('#tags', {

    createFilter: function(input) {

        if (category.value === 'numbers') {
            return /^[0-9]+$/.test(input);
        }

        return true;
    }
});

Валидация загружаемых данных

Данные, приходящие с сервера, также требуют проверки.

Фильтрация результатов поиска

new TomSelect('#users', {

    load: function(query, callback) {

        fetch('/api/users?q=' + query)
            .then(response => response.json())
            .then(data => {

                const filtered = data.filter(item => {
                    return item.active === true;
                });

                callback(filtered);
            });
    }
});

Проверка структуры объектов

При работе со сложными данными необходимо валидировать свойства объектов.

Проверка обязательных полей

new TomSelect('#products', {

    load: function(query, callback) {

        fetch('/api/products')
            .then(response => response.json())
            .then(data => {

                const valid = data.filter(item => {

                    return (
                        item.id &&
                        item.name &&
                        item.price
                    );
                });

                callback(valid);
            });
    }
});

Визуальное отображение ошибок

Проверка данных должна сопровождаться визуальной индикацией.

Добавление CSS-класса ошибки

const select = new TomSelect('#tags', {

    onChange: function(values) {

        if (values.length === 0) {

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

        } else {

            this.wrapper.classList.remove('invalid');
        }
    }
});
.invalid {
    border: 1px solid red;
}

Отображение текстовых сообщений

Вывод ошибки рядом с полем

<div class="field">
    <select id="skills" multiple></select>
    <div class="error"></div>
</div>
const error = document.querySelector('.error');

new TomSelect('#skills', {

    onChange: function(values) {

        if (values.length < 2) {

            error.textContent =
                'Минимум два значения';

        } else {

            error.textContent = '';
        }
    }
});

Централизованная система правил

В крупных приложениях правила удобно выносить в отдельную структуру.

Объект валидаторов

const validators = {

    minLength(value, length) {
        return value.length >= length;
    },

    hasNumber(value) {
        return /\d/.test(value);
    },

    noSpaces(value) {
        return !/\s/.test(value);
    }
};

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

new TomSelect('#codes', {

    createFilter: function(input) {

        return (
            validators.minLength(input, 5) &&
            validators.hasNumber(input) &&
            validators.noSpaces(input)
        );
    }
});

Композиция валидаторов

Проверки можно объединять в цепочки.

Последовательное выполнение правил

function validate(value, rules) {

    return rules.every(rule => rule(value));
}
const rules = [
    value => value.length > 3,
    value => /^[A-Z]/.test(value),
    value => /\d/.test(value)
];
new TomSelect('#codes', {

    createFilter: function(input) {

        return validate(input, rules);
    }
});

Возврат причин ошибки

Полезно не только отклонять значение, но и объяснять причину.

Подробная валидация

function validateCode(value) {

    if (value.length < 5) {
        return 'Минимум 5 символов';
    }

    if (!/\d/.test(value)) {
        return 'Требуется цифра';
    }

    return true;
}
const error = document.querySelector('.error');

new TomSelect('#codes', {

    createFilter: function(input) {

        const result = validateCode(input);

        if (result !== true) {
            error.textContent = result;
            return false;
        }

        error.textContent = '';

        return true;
    }
});

Валидация с debounce

При сложных проверках важно уменьшать количество вызовов.

Оптимизация проверки

function debounce(fn, delay) {

    let timeout;

    return function(...args) {

        clearTimeout(timeout);

        timeout = setTimeout(() => {
            fn.apply(this, args);
        }, delay);
    };
}
new TomSelect('#search', {

    onType: debounce(function(value) {

        console.log('Проверка:', value);

    }, 300)
});

Типичные ошибки при создании валидаторов

Потеря контекста this

createFilter: (input) => {
    return this.items.length < 5;
}

Стрелочная функция не получает собственный this.

Правильный вариант:

createFilter: function(input) {
    return this.items.length < 5;
}

Отсутствие нормализации

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

JavaScript
javascript
JAVASCRIPT

Проверка только на клиенте

Клиентская валидация не заменяет серверную проверку. Любые ограничения интерфейса могут быть обойдены.


Слишком тяжёлая проверка onType

Проверка при каждом вводе символа способна вызывать лаги интерфейса. Особенно опасны:

  • сложные регулярные выражения;
  • сетевые запросы;
  • большие циклы;
  • глубокие проверки объектов.

Архитектура масштабируемой системы валидации

В больших проектах удобно разделять:

Уровень Назначение
UI-проверка Быстрая реакция интерфейса
Бизнес-валидация Проверка логики приложения
Серверная проверка Финальная защита данных
Нормализация Приведение формата
Сообщения об ошибках Отображение причин

Подобная структура позволяет поддерживать сложные формы без дублирования логики и конфликтов между интерфейсом и сервером.