Валидация на стороне клиента

Валидация на стороне клиента при использовании Choices.js строится вокруг контроля состояния выбранных значений, так как библиотека заменяет стандартные элементы <select> и <input> на управляемый JavaScript-компонент. Это означает, что классические механизмы HTML5 частично перестают быть достаточными, и логика проверки переносится в обработчики событий и слой взаимодействия с формой.

Основная цель клиентской валидации в контексте Choices.js — обеспечить соответствие выбранных значений бизнес-правилам до отправки формы: количество элементов, допустимые значения, формат данных и ограничения по списку.


Базовые принципы валидации в Choices.js

Choices.js не выполняет валидацию автоматически. Библиотека предоставляет только управление состоянием выбора, поэтому проверка строится на внешней логике:

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

Ключевая сущность — экземпляр Choices, содержащий текущее состояние выбора:

const choices = new Choices('#select', {
  removeItemButton: true,
  shouldSort: false
});

Получение выбранных значений:

const selected = choices.getValue(true);

Проверка обязательного выбора значения

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

function validateRequired(choicesInstance) {
  const value = choicesInstance.getValue(true);
  return Array.isArray(value) ? value.length > 0 : value !== '';
}

Привязка к форме:

form.addEventListener('submit', (e) => {
  if (!validateRequired(choices)) {
    e.preventDefault();
    showError('Поле обязательно для заполнения');
  }
});

Ограничение количества выбранных элементов

Для мультивыборов часто задаются ограничения:

  • минимальное количество выбранных значений;
  • максимальное количество;
  • фиксированный диапазон.
function validateRange(choicesInstance, min, max) {
  const value = choicesInstance.getValue(true);
  const count = value.length;
  return count >= min && count <= max;
}

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

if (!validateRange(choices, 2, 5)) {
  showError('Необходимо выбрать от 2 до 5 элементов');
}

Choices.js позволяет отслеживать момент превышения лимита через события, но логика ограничения обычно реализуется вручную.


Реактивная проверка через события

Choices.js предоставляет события, позволяющие выполнять проверку в реальном времени:

  • addItem
  • removeItem
  • change
  • highlightItem
  • choice

Наиболее важные для валидации — addItem и removeItem.

choices.passedElement.element.addEventListener(
  'addItem',
  () => validateField()
);

choices.passedElement.element.addEventListener(
  'removeItem',
  () => validateField()
);

Реактивная функция проверки:

function validateField() {
  const valid = validateRange(choices, 1, 3);

  if (!valid) {
    setFieldState('error');
  } else {
    setFieldState('valid');
  }
}

Контроль допустимых значений

Choices.js позволяет динамически добавлять элементы, что требует защиты от недопустимых значений. Особенно актуально при использовании createItems: true.

Проверка может включать:

  • фильтрацию по регулярному выражению;
  • проверку длины строки;
  • запрет дубликатов;
  • сверку с белым списком.
const allowedPattern = /^[a-zA-Z0-9\s]{3,20}$/;

function validateItem(value) {
  return allowedPattern.test(value);
}

При создании элемента:

choices.passedElement.element.addEventListener('addItem', (event) => {
  const value = event.detail.value;

  if (!validateItem(value)) {
    choices.removeActiveItemsByValue(value);
    showError('Недопустимый формат значения');
  }
});

Проверка на дубликаты

Хотя Choices.js предотвращает часть дублирования в стандартных режимах, при кастомных источниках данных дубликаты могут появляться.

function hasDuplicate(choicesInstance, value) {
  const values = choicesInstance.getValue(true);
  return values.includes(value);
}

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

if (hasDuplicate(choices, newValue)) {
  showError('Значение уже выбрано');
}

Интеграция с HTML5 Constraint Validation API

Choices.js не всегда синхронизирован с нативной валидацией формы, поэтому требуется ручная синхронизация состояния.

const input = document.querySelector('#select');

function syncValidity() {
  if (!validateRequired(choices)) {
    input.setCustomValidity('Ошибка выбора');
  } else {
    input.setCustomValidity('');
  }
}

Привязка:

choices.passedElement.element.addEventListener('change', syncValidity);

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

form.reportValidity();

Блокировка отправки формы при ошибках

Распространённый подход — централизованная проверка всех Choices-инстансов перед submit:

const fields = [choicesA, choicesB, choicesC];

function validateForm() {
  return fields.every(c => validateRequired(c));
}

form.addEventListener('submit', (e) => {
  if (!validateForm()) {
    e.preventDefault();
    showError('Проверьте корректность заполнения полей');
  }
});

Асинхронная валидация значений

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

  • доступность значения;
  • соответствие бизнес-правилам;
  • проверка прав пользователя.
async function validateServer(value) {
  const res = await fetch('/validate', {
    method: 'POST',
    body: JSON.stringify({ value }),
    headers: { 'Content-Type': 'application/json' }
  });

  const data = await res.json();
  return data.valid;
}

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

let timer;

choices.passedElement.element.addEventListener('addItem', (e) => {
  clearTimeout(timer);

  timer = setTimeout(async () => {
    const valid = await validateServer(e.detail.value);

    if (!valid) {
      choices.removeActiveItemsByValue(e.detail.value);
      showError('Значение не проходит серверную проверку');
    }
  }, 300);
});

Валидация при создании пользовательских элементов

При включённой опции создания новых элементов важно контролировать ввод:

const choices = new Choices('#select', {
  createItems: true,
  duplicateItemsAllowed: false
});

Дополнительная логика:

choices.passedElement.element.addEventListener('addItem', (e) => {
  const value = e.detail.value.trim();

  if (value.length < 3) {
    choices.removeActiveItemsByValue(value);
    showError('Минимальная длина — 3 символа');
  }
});

Визуализация ошибок и управление состоянием

Choices.js не предоставляет встроенного UI для ошибок, поэтому применяется управление через DOM:

  • добавление классов;
  • вывод сообщений;
  • изменение рамки поля;
  • блокировка элементов формы.
function setFieldState(state) {
  const container = choices.containerOuter.element;

  container.classList.remove('is-valid', 'is-error');

  if (state === 'error') {
    container.classList.add('is-error');
  }

  if (state === 'valid') {
    container.classList.add('is-valid');
  }
}

Пример отображения сообщения:

function showError(message) {
  const errorBox = document.querySelector('.error-box');
  errorBox.textContent = message;
}

Синхронизация состояния при программных изменениях

При изменении значений через API важно запускать повторную проверку:

choices.setValue(['A', 'B']);

validateField();
syncValidity();

Игнорирование этого шага приводит к рассинхронизации UI и логики валидации.


Обработка очистки значения

Очистка состояния также должна учитывать правила:

choices.clearStore();

if (!validateRequired(choices)) {
  setFieldState('error');
}

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

В реальных сценариях применяется набор условий одновременно:

function validateAll() {
  const value = choices.getValue(true);

  if (value.length === 0) return false;
  if (value.length > 5) return false;
  if (value.some(v => v.length < 2)) return false;

  return true;
}

Типовые архитектурные подходы

Часто валидация выносится в отдельный слой:

  • валидаторы (pure functions);
  • контроллер формы;
  • слой UI-индикации;
  • обработчики Choices.js событий.

Такой подход позволяет масштабировать логику без привязки к конкретному экземпляру библиотеки.

const validators = {
  required: (v) => v.length > 0,
  max: (v, n) => v.length <= n
};

Итоговая модель поведения

Валидация Choices.js на клиенте строится как реактивная система, где:

  • состояние хранится внутри экземпляра Choices;
  • события используются как триггеры проверки;
  • правила реализуются вне библиотеки;
  • UI синхронизируется вручную через DOM;
  • серверная проверка подключается асинхронно при необходимости.