Клиентская валидация в интерфейсах с множественным выбором играет ключевую роль при работе с динамическими списками, тегами и асинхронными источниками данных. При использовании Tom Select контроль корректности выбранных значений перестаёт быть задачей только HTML-формы и переходит в область JavaScript-логики, где можно гибко управлять поведением компонента, ограничениями и пользовательскими ошибками.
Tom Select предоставляет структуру, в которой каждое добавление или удаление элемента может быть перехвачено через события. Это позволяет реализовать валидацию на уровне:
Ключевая идея заключается в том, что состояние селекта является источником истины, а валидация лишь реагирует на изменения этого состояния.
Самый простой сценарий — необходимость хотя бы одного выбранного
значения. HTML-атрибут required в сочетании с кастомным
компонентом часто недостаточен, поэтому логика дополняется
JavaScript.
const select = new TomSelect("#tags", {
onChange: function(value) {
validateSelect(this);
}
});
function validateSelect(instance) {
const isValid = instance.items.length > 0;
const input = instance.input;
input.setAttribute("aria-invalid", !isValid);
const wrapper = instance.wrapper;
wrapper.classList.toggle("is-invalid", !isValid);
}
Здесь важен момент: валидируется не DOM <select>,
а внутреннее состояние items.
Одно из самых частых требований — ограничение диапазона выбора. Tom Select позволяет реализовать это через конфигурацию и события.
const select = new TomSelect("#tags", {
maxItems: 5,
onItemAdd: function() {
enforceLimits(this);
},
onItemRemove: function() {
enforceLimits(this);
}
});
function enforceLimits(instance) {
const max = 5;
if (instance.items.length >= max) {
instance.control_input.disabled = true;
} else {
instance.control_input.disabled = false;
}
}
Поведение maxItems частично встроено, но дополнительная
логика нужна для UI-индикации и блокировки пользовательского ввода.
function validateMin(instance) {
const min = 2;
const valid = instance.items.length >= min;
instance.wrapper.classList.toggle("is-invalid", !valid);
}
Минимум чаще проверяется при отправке формы, а не в реальном времени.
Интеграция с формой — центральный элемент клиентской валидации. Tom Select не заменяет HTMLForm validation API, но расширяет его.
const form = document.querySelector("form");
const select = new TomSelect("#tags");
form.addEventListener("submit", function(e) {
const isValid = select.items.length >= 1 && select.items.length <= 5;
if (!isValid) {
e.preventDefault();
select.wrapper.classList.add("is-invalid");
}
});
Такой подход гарантирует, что даже при обходе UI-ограничений данные не будут отправлены некорректными.
Одной из особенностей Tom Select является возможность создания новых
элементов через create: true. Это требует дополнительной
проверки входных данных.
const select = new TomSelect("#tags", {
create: function(input) {
return validateTag(input) ? {
value: input,
text: input
} : false;
}
});
function validateTag(value) {
const regex = /^[a-zA-Z0-9_-]{3,20}$/;
return regex.test(value);
}
Здесь валидация выполняется до добавления элемента в список, что предотвращает попадание некорректных значений в состояние компонента.
Хотя Tom Select по умолчанию не позволяет дублировать выбранные элементы, при кастомной логике или динамических данных дубликаты могут появляться.
const select = new TomSelect("#tags", {
onItemAdd: function(value) {
const duplicates = this.items.filter(v => v === value);
if (duplicates.length > 1) {
this.removeItem(value);
}
}
});
Более строгий подход — блокировка ещё на этапе добавления:
create: function(input) {
if (this.items.includes(input)) {
return false;
}
return { value: input, text: input };
}
Клиентская валидация должна сопровождаться визуальной обратной связью. Обычно используются CSS-классы:
.is-invalid .ts-control {
border-color: #e74c3c;
}
.is-invalid .ts-control:focus {
box-shadow: 0 0 0 2px rgba(231, 76, 60, 0.2);
}
И JavaScript-логика переключения состояния:
function setInvalid(instance, state) {
instance.wrapper.classList.toggle("is-invalid", state);
}
Ключевые события, используемые при валидации:
onChange — изменение общего состоянияonItemAdd — добавление элементаonItemRemove — удаление элементаonType — ввод текста пользователемПример комплексной логики:
const select = new TomSelect("#tags", {
onChange: validate,
onItemAdd: validate,
onItemRemove: validate,
onType: function() {
clearError(this);
}
});
function validate(instance) {
const valid =
instance.items.length >= 1 &&
instance.items.length <= 5;
setInvalid(instance, !valid);
}
function clearError(instance) {
instance.wrapper.classList.remove("is-invalid");
}
Хотя Tom Select не является нативным input-элементом, его можно синхронизировать с HTML5 validation API.
const select = new TomSelect("#tags");
const input = select.input;
function syncValidity(instance) {
const valid = instance.items.length > 0;
if (!valid) {
input.setCustomValidity("Выберите хотя бы один элемент");
} else {
input.setCustomValidity("");
}
}
Это позволяет использовать стандартные механизмы браузера:
reportValidity(), checkValidity().
При работе с динамическими данными важно контролировать допустимые значения, особенно если данные приходят извне.
const allowed = ["js", "css", "html"];
const select = new TomSelect("#tags", {
create: false,
onItemAdd: function(value) {
if (!allowed.includes(value)) {
this.removeItem(value);
}
}
});
Такой подход предотвращает внедрение недопустимых значений даже при манипуляции DOM.
Иногда требуется клиентская проверка через локальные данные или кеш, без обращения к серверу.
const cache = new Set(["existing-tag"]);
const select = new TomSelect("#tags", {
create: function(input) {
if (cache.has(input)) {
return false;
}
return { value: input, text: input };
}
});
Хотя это похоже на серверную проверку, логика остаётся на клиенте и выполняется синхронно.
При критических нарушениях правил можно блокировать ввод полностью:
function lockSelect(instance, reason) {
instance.disable();
instance.wrapper.setAttribute("data-error", reason);
}
Разблокировка выполняется после исправления состояния:
function unlockSelect(instance) {
instance.enable();
instance.wrapper.removeAttribute("data-error");
}
Главная сложность клиентской валидации с Tom Select заключается в синхронизации трёх уровней:
items)wrapper, control)FormData, submit event)Корректная архитектура предполагает, что любое изменение проходит через единый слой валидации:
function applyChange(instance, validator) {
const result = validator(instance.items);
instance.wrapper.classList.toggle("is-invalid", !result);
return result;
}
Если элементы подгружаются асинхронно, важно проверять их до добавления:
const select = new TomSelect("#tags", {
load: function(query, callback) {
fetch("/api/tags?q=" + query)
.then(r => r.json())
.then(data => {
callback(data.filter(validateRemoteItem));
});
}
});
function validateRemoteItem(item) {
return item.active === true;
}
Поле ввода внутри Tom Select может быть источником некорректных
значений, особенно при включённом create.
const select = new TomSelect("#tags", {
create: true,
onType: function(str) {
if (str.length > 20) {
this.control_input.value = str.slice(0, 20);
}
}
});
Такой подход предотвращает переполнение допустимого формата ещё до создания элемента.