Событие beforeChange

Событие beforeChange перехватывает момент до применения нового значения в компоненте Slim Select и позволяет вмешаться в процесс обновления выбранных данных. Это ключевая точка управления состоянием, когда ещё возможно отменить изменение, проверить его корректность или модифицировать входящие данные до их фиксации в DOM и внутреннем состоянии экземпляра.

В Slim Select изменение значения проходит несколько последовательных этапов:

  • инициирование изменения (клик по опции, ввод, программное изменение)
  • вызов beforeChange
  • применение нового значения (если не было отмены)
  • вызов afterChange

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

Сигнатура обработчика

Обработчик beforeChange обычно получает контекст текущего экземпляра Slim Select и информацию о предполагаемом изменении:

new SlimSelect({
  select: '#select',
  events: {
    beforeChange: (newValue, oldValue, slim) => {
      // логика обработки
    }
  }
});

Типовая структура параметров:

  • newValue — значение, которое планируется установить
  • oldValue — текущее значение до изменения
  • slim — экземпляр Slim Select, позволяющий обращаться к API компонента

В некоторых реализациях параметры могут быть сгруппированы в объект, но логика остаётся аналогичной: предоставляется доступ к текущему и новому состоянию.

Механизм отмены изменения

Основная функциональность beforeChange заключается в возможности отменить обновление. Для этого обработчик должен вернуть false.

beforeChange: (newValue, oldValue, slim) => {
  if (newValue.includes('forbidden')) {
    return false;
  }
}

При возврате false:

  • новое значение не применяется
  • DOM-состояние остаётся прежним
  • событие afterChange не вызывается
  • пользовательское действие визуально откатывается

Любое другое возвращаемое значение (или отсутствие return) означает разрешение изменения.

Доступ к данным и контексту

Через slim доступен полный API экземпляра Slim Select, включая:

  • текущее значение (slim.getSelected())
  • список опций
  • методы программного управления (set, enable, disable)

Это позволяет строить сложную логику контроля выбора:

beforeChange: (newValue, oldValue, slim) => {
  const selected = slim.getSelected();

  if (selected.length > 3) {
    return false;
  }
}

Практические сценарии использования

Ограничение выбора по бизнес-правилам

Событие часто используется для реализации ограничений, которые невозможно выразить через HTML-атрибуты:

beforeChange: (newValue, oldValue, slim) => {
  if (Array.isArray(newValue) && newValue.length > 5) {
    return false;
  }
}

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

Фильтрация запрещённых значений

const blocked = ['admin', 'root', 'system'];

beforeChange: (newValue) => {
  if (blocked.includes(newValue)) {
    return false;
  }
}

Подобная логика используется для предотвращения выбора служебных или недопустимых опций.

Зависимость от текущего состояния

beforeChange: (newValue, oldValue, slim) => {
  const current = slim.getSelected();

  if (current.includes('locked') && newValue !== 'unlock') {
    return false;
  }
}

Модификация значения перед применением

В ряде сценариев требуется не отмена, а коррекция данных до применения. Хотя beforeChange в Slim Select в первую очередь предназначен для валидации, некоторые версии допускают возврат изменённого значения.

beforeChange: (newValue) => {
  if (typeof newValue === 'string') {
    return newValue.trim().toLowerCase();
  }
}

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

Асинхронная валидация

Прямой асинхронный beforeChange в большинстве реализаций Slim Select не поддерживается. Однако возможен обходной паттерн с предварительной блокировкой изменения и внешней проверкой:

let isValidating = false;

beforeChange: (newValue) => {
  if (isValidating) return false;

  isValidating = true;

  fakeApiCheck(newValue).then(result => {
    isValidating = false;

    if (result.valid) {
      slim.set(result.value);
    }
  });

  return false;
}

В такой схеме изменение всегда блокируется до завершения внешней проверки, а затем применяется вручную.

Взаимодействие с afterChange

beforeChange тесно связан с последующим событием afterChange. Логика разделяется следующим образом:

  • beforeChange — контроль и фильтрация
  • afterChange — реакция на уже применённое состояние
events: {
  beforeChange: (newValue) => {
    if (!newValue) return false;
  },

  afterChange: (newValue) => {
    console.log('значение изменено:', newValue);
  }
}

Важно учитывать, что afterChange не срабатывает при отмене в beforeChange.

Типичные ошибки при использовании

Побочные эффекты внутри beforeChange

Изменение состояния селекта внутри beforeChange без контроля может привести к циклическим вызовам:

beforeChange: (newValue, oldValue, slim) => {
  slim.set('value'); // риск рекурсии
}

Неправильная интерпретация return

Отсутствие явного return false часто приводит к тому, что разработчики ожидают блокировки, но изменение всё равно применяется.

beforeChange: () => {
  if (true) {
    false; // не блокирует изменение
  }
}

Игнорирование массива значений

В мультиселектах newValue может быть массивом, и проверка должна учитывать это:

beforeChange: (newValue) => {
  if (Array.isArray(newValue) && newValue.includes('invalid')) {
    return false;
  }
}

Контроль сложных сценариев выбора

beforeChange часто используется как центральный узел бизнес-логики формы:

  • динамическое ограничение опций
  • зависимые поля
  • предотвращение конфликтующих комбинаций
  • контроль прав доступа
beforeChange: (newValue, oldValue, slim) => {
  const userRole = getUserRole();

  if (userRole !== 'admin' && newValue === 'admin-mode') {
    return false;
  }

  if (newValue === 'reset') {
    slim.set([]);
    return false;
  }
}

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