Кастомные правила валидации в Choices.js строятся вокруг перехвата ключевых точек жизненного цикла элемента и управления тем, какие значения допускаются к добавлению в состояние компонента. Библиотека изначально ориентирована на расширяемость, поэтому вместо единого механизма валидации используется набор хуков, фильтров и событий, позволяющих реализовать как простые ограничения, так и сложные бизнес-правила.
Внутренняя модель данных Choices.js разделяет входные значения на несколько уровней:
Кастомная валидация может быть внедрена на каждом этапе, однако основной контроль сосредоточен вокруг добавления элемента в выбранные значения.
Ключевая идея заключается в том, что библиотека не навязывает строгую схему данных, а предоставляет механизм фильтрации:
В Choices.js нет единственного “validator”, вместо него используется комбинация механизмов:
addItemFilter — основной фильтр перед добавлением
значенияaddItem, search,
choice, removeItem)maxItemCount,
duplicateItemsAllowed)Эта модель позволяет строить каскадную валидацию, где каждый слой отвечает за свою часть логики.
Одним из ключевых механизмов является addItemFilter. Он
позволяет полностью контролировать, будет ли значение принято в список
выбранных элементов.
Типовая реализация строится вокруг функции, возвращающей отфильтрованный или модифицированный input.
const choices = new Choices('#select', {
addItemFilter: (value) => {
const trimmed = value.trim();
const isEmpty = trimmed.length === 0;
const isTooShort = trimmed.length < 3;
if (isEmpty) {
return false;
}
if (isTooShort) {
return false;
}
return trimmed;
}
});
Поведение фильтра:
false блокирует добавлениеТаким образом, addItemFilter фактически выполняет роль
синхронного валидатора и нормализатора данных одновременно.
Распространённый сценарий — контроль соответствия значений регулярным выражениям. Это особенно актуально при использовании Choices.js как компонента для тегов, email-адресов или кодов.
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const choices = new Choices('#emails', {
addItemFilter: (value) => {
const normalized = value.toLowerCase().trim();
if (!emailPattern.test(normalized)) {
return false;
}
return normalized;
}
});
Такой подход позволяет реализовать строгую валидацию без необходимости внешних библиотек.
Хотя Choices.js содержит встроенные механизмы предотвращения дублирования, кастомная логика может быть расширена за счёт проверки текущего состояния инстанса.
const choices = new Choices('#tags', {
addItemFilter: (value) => {
const normalized = value.trim().toLowerCase();
const existing = choices.getValue(true);
const alreadyExists = existing.some(
item => item.value.toLowerCase() === normalized
);
if (alreadyExists) {
return false;
}
return normalized;
}
});
Здесь используется доступ к текущему состоянию через
getValue(true), что позволяет выполнять проверку контекста
перед добавлением.
Ограничение количества выбранных элементов часто реализуется через
параметр maxItemCount, однако более гибкая логика
достигается через комбинацию фильтра и состояния.
const MAX = 5;
const choices = new Choices('#multi', {
addItemFilter: (value) => {
const current = choices.getValue(true);
if (current.length >= MAX) {
return false;
}
return value.trim();
}
});
Такой подход позволяет динамически менять ограничения, учитывать внешние условия и состояние интерфейса.
Choices.js предоставляет набор событий, которые позволяют реализовать пост-валидацию или реакцию на изменения состояния.
Наиболее важные события:
addItemremoveItemsearchhighlightItemchoiceПример использования события для дополнительной проверки:
const choices = new Choices('#input');
choices.passedElement.element.addEventListener('addItem', (event) => {
const value = event.detail.value;
if (value.includes('test')) {
choices.removeActiveItemsByValue(value);
}
});
Этот механизм применяется для ситуаций, когда валидация должна приводить не к блокировке, а к коррекции состояния после добавления.
Choices.js не предоставляет встроенного async-validator API, однако асинхронная проверка может быть реализована через комбинацию событий и временного отката состояния.
const choices = new Choices('#async');
choices.passedElement.element.addEventListener('addItem', async (event) => {
const value = event.detail.value;
const isValid = await fakeServerValidation(value);
if (!isValid) {
choices.removeActiveItemsByValue(value);
}
});
async function fakeServerValidation(value) {
return new Promise(resolve => {
setTimeout(() => {
resolve(value.length > 2);
}, 300);
});
}
Такая схема позволяет интегрировать серверную проверку без изменения внутренней логики библиотеки.
Часто валидация тесно связана с нормализацией. Choices.js допускает модификацию значения до его добавления, что позволяет унифицировать формат данных.
Типовые операции нормализации:
const choices = new Choices('#normalized', {
addItemFilter: (value) => {
return value
.trim()
.replace(/\s+/g, '_')
.toLowerCase();
}
});
При включённой возможности создания новых элементов
(createItems) валидация становится критической частью
контроля качества данных.
Типичный сценарий:
const choices = new Choices('#create', {
createItems: true,
addItemFilter: (value) => {
const cleaned = value.trim();
if (cleaned.length < 2) {
return false;
}
if (cleaned.length > 20) {
return false;
}
return cleaned;
}
});
Это позволяет ограничивать пользовательские значения по длине, формату и содержанию.
На практике валидация редко ограничивается одной функцией. Чаще применяется каскадный подход:
const choices = new Choices('#complex', {
addItemFilter: (value) => {
const normalized = value.trim().toLowerCase();
if (!/^[a-z0-9_-]+$/.test(normalized)) {
return false;
}
const current = choices.getValue(true);
if (current.length >= 10) {
return false;
}
if (current.some(i => i.value === normalized)) {
return false;
}
return normalized;
}
});
Choices.js не содержит встроенного механизма отображения ошибок, поэтому визуальная обратная связь строится через внешние DOM-элементы или модификацию классов контейнера.
Типовой подход:
const container = document.querySelector('.choices');
choices.passedElement.element.addEventListener('addItem', (event) => {
const value = event.detail.value;
if (value.length < 3) {
container.classList.add('has-error');
} else {
container.classList.remove('has-error');
}
});
Таким образом, валидация отделяется от представления, но может влиять на UI через стандартные DOM-механизмы.
При построении сложной логики важно учитывать порядок выполнения:
addItemFilter выполняется до добавленияsetValue обходят часть пользовательских
ограниченийЭто означает, что строгая валидация должна дублироваться в нескольких слоях, если требуется абсолютный контроль над данными.
В реальных сценариях Choices.js используется как UI-слой над доменной моделью. Валидация часто отражает бизнес-правила:
const ALLOWED = ['admin', 'editor', 'viewer'];
const choices = new Choices('#roles', {
addItemFilter: (value) => {
const normalized = value.trim().toLowerCase();
if (!ALLOWED.includes(normalized)) {
return false;
}
return normalized;
}
});
Такой подход превращает компонент выбора в строго контролируемый интерфейс ввода данных без необходимости дополнительной серверной валидации на каждом шаге UI.