Кастомные правила валидации

Кастомные правила валидации в Choices.js строятся вокруг перехвата ключевых точек жизненного цикла элемента и управления тем, какие значения допускаются к добавлению в состояние компонента. Библиотека изначально ориентирована на расширяемость, поэтому вместо единого механизма валидации используется набор хуков, фильтров и событий, позволяющих реализовать как простые ограничения, так и сложные бизнес-правила.


Внутренняя модель данных Choices.js разделяет входные значения на несколько уровней:

  • сырые данные (input value)
  • обработанные choices (варианты выбора)
  • выбранные items (selected items)
  • поисковый фильтр (search state)

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

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

  • до добавления элемента
  • во время поиска
  • при создании пользовательского значения
  • при вставке (paste)
  • при программном вызове addItem

Точки расширения для валидации

В Choices.js нет единственного “validator”, вместо него используется комбинация механизмов:

  • addItemFilter — основной фильтр перед добавлением значения
  • события (addItem, search, choice, removeItem)
  • параметры ограничения состояния (maxItemCount, duplicateItemsAllowed)
  • кастомные обработчики пользовательского ввода (через конфигурацию и события)

Эта модель позволяет строить каскадную валидацию, где каждый слой отвечает за свою часть логики.


Фильтрация добавляемых значений через addItemFilter

Одним из ключевых механизмов является 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 предоставляет набор событий, которые позволяют реализовать пост-валидацию или реакцию на изменения состояния.

Наиболее важные события:

  • addItem
  • removeItem
  • search
  • highlightItem
  • choice

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

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();
  }
});

Валидация пользовательского ввода (custom item creation)

При включённой возможности создания новых элементов (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;
  }
});

Это позволяет ограничивать пользовательские значения по длине, формату и содержанию.


Комбинирование нескольких уровней проверки

На практике валидация редко ограничивается одной функцией. Чаще применяется каскадный подход:

  1. предварительная нормализация
  2. проверка формата
  3. проверка дубликатов
  4. проверка состояния компонента
  5. пост-валидация через события
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;
  }
});

Управление ошибочными состояниями через UI-логику

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-слой над доменной моделью. Валидация часто отражает бизнес-правила:

  • уникальные идентификаторы
  • ограничения по доменам email
  • whitelist значений
  • синхронизация с сервером
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.