Валидация пользовательского ввода

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


Модель ввода данных и точки валидации

Tom Select работает с двумя основными типами данных:

  • предопределённые опции (options) — элементы, загруженные заранее или через AJAX;
  • создаваемые пользователем элементы (create mode) — значения, вводимые вручную.

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

  • фильтрация ввода до создания элемента;
  • проверка перед добавлением в список выбранных значений;
  • постобработка уже добавленных элементов;
  • асинхронная проверка через сервер.

Ключевые точки расширения:

  • create — разрешение создания новых элементов;
  • createFilter — фильтрация допустимых значений;
  • onItemAdd — контроль после добавления;
  • onType — обработка ввода пользователя;
  • score — кастомная логика поиска и релевантности.

Ограничение создания новых значений

Механизм create управляет возможностью добавления новых элементов, не присутствующих в списке options.

new TomSelect("#select", {
  create: true
});

Более строгая форма валидации:

new TomSelect("#select", {
  create: function(input) {
    const value = input.trim();

    if (value.length < 3) return false;
    if (!/^[a-zA-Z0-9_]+$/.test(value)) return false;

    return {
      value: value,
      text: value
    };
  }
});

В этом варианте функция create заменяет стандартное поведение и позволяет реализовать полноценную проверку входного значения перед созданием элемента.


Фильтрация допустимых значений через createFilter

createFilter определяет, какие значения допустимо превращать в новые элементы.

new TomSelect("#select", {
  create: true,
  createFilter: function(input) {
    return input.length >= 2;
  }
});

Расширенный пример с проверкой формата:

new TomSelect("#select", {
  create: true,
  createFilter: function(input) {
    const trimmed = input.trim();

    const isValidLength = trimmed.length >= 3;
    const matchesPattern = /^[a-z0-9-]+$/i.test(trimmed);
    const noSpacesOnly = trimmed !== "";

    return isValidLength && matchesPattern && noSpacesOnly;
  }
});

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

Одной из ключевых задач валидации является предотвращение повторного добавления одинаковых значений.

Встроенная логика может быть расширена через onItemAdd:

new TomSelect("#select", {
  create: true,

  onItemAdd: function(value) {
    const items = this.items;

    const duplicates = items.filter(v => v === value);
    if (duplicates.length > 1) {
      this.removeItem(value, true);
    }
  }
});

Более корректный подход — блокировка до добавления:

new TomSelect("#select", {
  create: function(input) {
    const value = input.trim();

    if (this.options[value]) {
      return false;
    }

    return {
      value,
      text: value
    };
  }
});

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

Валидация часто включает контроль общего количества выбранных значений.

new TomSelect("#select", {
  maxItems: 5,

  onItemAdd: function() {
    if (this.items.length > 5) {
      this.removeItem(this.items[this.items.length - 1]);
    }
  }
});

Гораздо предпочтительнее использовать встроенный параметр:

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

Валидация через регулярные выражения

Регулярные выражения позволяют задавать строгие правила для входных данных.

Пример: только email-подобные значения

new TomSelect("#select", {
  create: function(input) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

    if (!emailRegex.test(input)) return false;

    return {
      value: input,
      text: input
    };
  }
});

Пример: ограничение по доменам

const allowedDomains = ["example.com", "site.org"];

new TomSelect("#select", {
  create: function(input) {
    const domain = input.split("@")[1];

    if (!allowedDomains.includes(domain)) {
      return false;
    }

    return {
      value: input,
      text: input
    };
  }
});

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

Для сложных сценариев используется проверка через сервер.

new TomSelect("#select", {
  create: function(input, callback) {
    fetch(`/validate?value=${encodeURIComponent(input)}`)
      .then(res => res.json())
      .then(data => {
        if (!data.valid) {
          callback(false);
          return;
        }

        callback({
          value: input,
          text: input
        });
      })
      .catch(() => callback(false));
  }
});

Асинхронная валидация позволяет:

  • проверять уникальность на сервере;
  • фильтровать запрещённые слова;
  • учитывать бизнес-логику.

Перехват ввода через onType

onType используется для предварительной обработки пользовательского ввода.

new TomSelect("#select", {
  onType: function(str) {
    const sanitized = str.replace(/[^a-zA-Z0-9 ]/g, "");

    this.setTextboxValue(sanitized);
  }
});

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

new TomSelect("#select", {
  onType: function(str) {
    const normalized = str
      .toLowerCase()
      .replace(/\s+/g, " ")
      .trim();

    this.setTextboxValue(normalized);
  }
});

Валидация при добавлении элементов

onItemAdd позволяет блокировать или корректировать добавленные значения.

new TomSelect("#select", {
  onItemAdd: function(value) {
    if (value.length < 3) {
      this.removeItem(value);
    }
  }
});

Дополнительная защита:

new TomSelect("#select", {
  onItemAdd: function(value) {
    const forbidden = ["admin", "root", "null"];

    if (forbidden.includes(value.toLowerCase())) {
      this.removeItem(value);
    }
  }
});

Использование нормализации данных

Перед сохранением значений часто требуется унификация формата.

function normalize(value) {
  return value
    .trim()
    .toLowerCase()
    .replace(/\s+/g, "_");
}

new TomSelect("#select", {
  create: function(input) {
    const normalized = normalize(input);

    return {
      value: normalized,
      text: input.trim()
    };
  }
});

Комбинированная стратегия валидации

Реальные сценарии требуют объединения нескольких уровней проверки:

new TomSelect("#select", {
  create: function(input) {
    const value = input.trim();

    if (value.length < 3) return false;
    if (!/^[a-z0-9-]+$/i.test(value)) return false;
    if (this.items.includes(value)) return false;

    return {
      value,
      text: value
    };
  },

  createFilter: function(input) {
    return input.trim().length >= 3;
  },

  onItemAdd: function(value) {
    const forbidden = ["test", "demo"];

    if (forbidden.includes(value.toLowerCase())) {
      this.removeItem(value);
    }
  }
});

Валидация через кастомные данные options

Иногда проверка выполняется не на вводе, а на уровне существующих options.

new TomSelect("#select", {
  options: [
    { value: "1", text: "Option 1", disabled: true },
    { value: "2", text: "Option 2" }
  ]
});

Использование disabled позволяет исключать выбор без дополнительной логики.


Защита от некорректного состояния UI

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

new TomSelect("#select", {
  onInitialize: function() {
    this.refreshOptions(false);
  },

  onChange: function(value) {
    if (!value) return;

    if (Array.isArray(value) && value.length === 0) {
      this.clear(true);
    }
  }
});

Валидация в условиях удалённого поиска

При использовании remote data источников важно валидировать результаты перед отображением:

new TomSelect("#select", {
  load: function(query, callback) {
    fetch(`/search?q=${query}`)
      .then(res => res.json())
      .then(data => {
        const filtered = data.filter(item => item.active);
        callback(filtered);
      })
      .catch(() => callback([]));
  }
});

Контроль безопасности входных данных

Дополнительный уровень защиты включает очистку от потенциально опасных значений:

function sanitize(str) {
  return str
    .replace(/</g, "")
    .replace(/>/g, "")
    .replace(/"/g, "");
}

new TomSelect("#select", {
  create: function(input) {
    const clean = sanitize(input);

    return {
      value: clean,
      text: clean
    };
  }
});