Запрет дублирующихся значений

Tom Select построен вокруг модели выбора элементов, где каждое добавленное значение попадает в внутренний список items, а идентификация осуществляется через valueField. Дублирование возникает не на уровне DOM, а на уровне логики добавления: один и тот же value может быть повторно добавлен, если не наложены ограничения в конфигурации или обработчиках событий.

Дублирование чаще всего проявляется в режимах:

  • plugins: ['remove_button'] при повторном добавлении через addItem
  • кастомные create-значения (теги)
  • асинхронные источники (load), где совпадающие value приходят повторно
  • программное добавление через addItem() без проверки текущего состояния

Внутренняя модель не запрещает дубли сама по себе, если не задана явная логика фильтрации.

Базовая модель уникальности

Уникальность в Tom Select определяется значением поля:

  • valueField — ключ уникальности
  • items[] — массив выбранных значений

Если valueField совпадает, но проверка не реализована, библиотека может добавить элемент повторно в некоторых сценариях (особенно при ручном вызове API).

Ограничение через проверку items

Наиболее прямой способ — проверка перед добавлением:

const control = new TomSelect('#select', {
  valueField: 'id',
  labelField: 'title',
  searchField: 'title',
  options: [],
  items: []
});

function addUniqueItem(value) {
  if (control.items.includes(value)) {
    return;
  }
  control.addItem(value);
}

Такой подход блокирует дубли на уровне API-слоя.

Перехват события item_add

Более централизованная стратегия — контроль через событие:

const control = new TomSelect('#select', {
  valueField: 'id',
  labelField: 'title',
  searchField: 'title',

  onItemAdd(value) {
    const occurrences = control.items.filter(v => v === value).length;

    if (occurrences > 1) {
      control.removeItem(value, true);
    }
  }
});

Здесь реализуется пост-фильтрация, устраняющая повторное добавление.

Запрет дубликатов через фильтрацию options

Если источником данных является фиксированный список:

const options = [
  { id: 1, title: 'Alpha' },
  { id: 2, title: 'Beta' }
];

const control = new TomSelect('#select', {
  options: options,
  valueField: 'id',
  labelField: 'title',
  searchField: 'title',

  create: false
});

При create: false исключаются пользовательские значения, что уже снижает риск появления повторов.

Контроль через createFilter

Для тегов и пользовательских значений критичен createFilter:

const control = new TomSelect('#tags', {
  create: true,

  createFilter: function(input) {
    return !this.options.hasOwnProperty(input);
  }
});

Здесь используется проверка существования ключа в options, что предотвращает повторное создание одинаковых значений.

Асинхронная загрузка и дубликаты

При load(query, callback) дубликаты часто появляются из-за повторного возврата одинаковых элементов от сервера:

load: function(query, callback) {
  fetch('/api/search?q=' + encodeURIComponent(query))
    .then(res => res.json())
    .then(data => {
      const filtered = data.filter(item => !this.options[item.id]);
      callback(filtered);
    });
}

Фильтрация по this.options предотвращает повторное добавление уже существующих элементов.

Защита через нормализацию данных

Дубли могут возникать из-за различий в типах:

  • "1" и 1
  • пробелы "tag" и "tag "

Решение — нормализация:

function normalize(value) {
  return String(value).trim().toLowerCase();
}

const control = new TomSelect('#select', {
  onItemAdd(value) {
    const normalized = normalize(value);

    const duplicates = control.items.filter(
      v => normalize(v) === normalized
    );

    if (duplicates.length > 1) {
      control.removeItem(value, true);
    }
  }
});

Программное добавление и защита addItem

addItem() — основной источник скрытых дубликатов:

function safeAdd(value) {
  if (!control.items.includes(value)) {
    control.addItem(value, true);
  }
}

Флаг silent (второй аргумент) снижает количество побочных эффектов при массовых операциях.

Ограничение через disable уже выбранных элементов

Дополнительный UX-подход — отключение уже выбранных опций:

onItemAdd: function(value) {
  if (this.options[value]) {
    this.getOption(value).classList.add('disabled');
  }
},

onItemRemove: function(value) {
  const opt = this.getOption(value);
  if (opt) opt.classList.remove('disabled');
}

Это не технический запрет, но визуально блокирует повторный выбор.

Использование maxItems как косвенной защиты

Хотя maxItems не предотвращает дубликаты напрямую, он ограничивает рост массива:

new TomSelect('#select', {
  maxItems: 5
});

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

Сравнение стратегий предотвращения дублей

  • Проверка items.includes — минимальная и надёжная
  • onItemAdd фильтрация — централизованный контроль
  • createFilter — защита тегов
  • фильтрация load — обязательна при API-данных
  • нормализация значений — защита от логических коллизий

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