Стандартных механизмов проверки данных в Tom Select
часто оказывается недостаточно для сложных интерфейсов. Ограничение
длины строки, запрет пустых значений или базовая фильтрация символов
покрывают лишь простые сценарии. В реальных приложениях требуется:
Пользовательские правила позволяют внедрять собственную логику проверки практически на любом этапе работы компонента.
Проверка значений обычно строится вокруг следующих механизмов:
| Механизм | Назначение |
|---|---|
createFilter |
Проверка новых значений перед созданием |
onItemAdd |
Контроль добавленных элементов |
onChange |
Общая проверка состояния |
onType |
Анализ вводимого текста |
addOption() |
Валидация добавляемых данных |
load() |
Проверка данных сервера |
render |
Отображение ошибок |
| DOM API | Интеграция с HTML5-валидацией |
Параметр 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);
}
});
Допустимы только английские буквы.
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;
}
});
Условия:
Метод create позволяет полностью контролировать создание
элемента.
new TomSelect('#tags', {
create: function(input, callback) {
if (input.length < 3) {
callback();
return;
}
callback({
value: input,
text: input
});
}
});
Если callback() вызывается без объекта — создание
отменяется.
Один из важнейших сценариев — проверка на сервере.
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('Недостаточно значений');
}
}
});
Tom Select работает поверх обычного элемента формы, поэтому возможна интеграция с браузерной системой проверки.
<select id="country" required>
<option value="">Выберите страну</option>
<option value="kz">Казахстан</option>
</select>
new TomSelect('#country');
Можно использовать API браузера.
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);
});
}
});
Проверка данных должна сопровождаться визуальной индикацией.
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;
}
});
При сложных проверках важно уменьшать количество вызовов.
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)
});
createFilter: (input) => {
return this.items.length < 5;
}
Стрелочная функция не получает собственный this.
Правильный вариант:
createFilter: function(input) {
return this.items.length < 5;
}
Без приведения регистра могут появляться дубликаты:
JavaScript
javascript
JAVASCRIPT
Клиентская валидация не заменяет серверную проверку. Любые ограничения интерфейса могут быть обойдены.
Проверка при каждом вводе символа способна вызывать лаги интерфейса. Особенно опасны:
В больших проектах удобно разделять:
| Уровень | Назначение |
|---|---|
| UI-проверка | Быстрая реакция интерфейса |
| Бизнес-валидация | Проверка логики приложения |
| Серверная проверка | Финальная защита данных |
| Нормализация | Приведение формата |
| Сообщения об ошибках | Отображение причин |
Подобная структура позволяет поддерживать сложные формы без дублирования логики и конфликтов между интерфейсом и сервером.