Обратная совместимость

Переход с устаревших решений для <select>-элементов на Tom Sel ect редко ограничивается простой заменой инициализации. В большинстве проектов библиотека интегрирована в существующую архитектуру: используются старые события, кастомные плагины, специфические CSS-классы, серверные шаблоны, jQuery-подходы и внутренние API. Обратная совместимость становится критически важной задачей при постепенной миграции крупных интерфейсов.

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


Совместимость с обычным HTML <select>

Tom Select сохраняет базовую совместимость со стандартными элементами формы. Это означает:

  • поддерживается обычный <select>;
  • сохраняется работа <option>;
  • поддерживаются <optgroup>;
  • значения участвуют в отправке формы;
  • совместим с HTML-валидацией браузера.

Пример обычного элемента:

<select id="country">
    <option value="kz">Казахстан</option>
    <option value="ru">Россия</option>
    <option value="uz">Узбекистан</option>
</select>

Инициализация:

new TomSelect('#country');

После инициализации исходный <select> не удаляется. Библиотека скрывает его и синхронизирует состояние с пользовательским интерфейсом.

Это особенно важно для:

  • старых серверных приложений;
  • PHP-форм;
  • SSR-шаблонов;
  • систем без SPA;
  • legacy-кода.

Совместимость с серверной обработкой форм

Tom Select не меняет механизм отправки формы. Сервер продолжает получать значения так же, как и при использовании обычного <select>.

Пример:

<select id="tags" name="tags[]" multiple>
    <option value="js" selected>JavaScript</option>
    <option value="css" selected>CSS</option>
</select>

После отправки формы сервер получит:

tags[]=js
tags[]=css

Это позволяет внедрять библиотеку без изменения backend-логики.


Совместимость с jQuery-кодом

Tom Select написан без зависимости от jQuery. Однако во многих старых проектах логика построена именно вокруг него.

Инициализация через jQuery

Можно создать адаптер:

$.fn.tomselect = function(options) {
    return this.each(function() {
        if (!this.tomselect) {
            new TomSelect(this, options);
        }
    });
};

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

$('#country').tomselect();

Такой подход помогает сохранить существующий стиль кода.


Эмуляция старого API

Во многих legacy-проектах код ожидает определённые методы.

Например:

$('#country')[0].selectize.clear();

Для обратной совместимости можно создать alias:

const control = new TomSelect('#country');

control.selectize = control;

Теперь старый код продолжит работать:

control.selectize.clear();

Совместимость с Selectize.js

Tom Select изначально создавался как форк Selectize.js. Большая часть API осталась прежней.

Совместимые настройки

Многие конфигурации работают без изменений:

new TomSelect('#users', {
    valueField: 'id',
    labelField: 'name',
    searchField: 'name',
    create: true,
    maxItems: 5
});

Совместимые методы

Большинство методов имеют одинаковые имена:

control.addOption();
control.clear();
control.setValue();
control.getValue();
control.disable();
control.enable();

Совместимые события

События также largely сохранены:

control.on('change', value => {
    console.log(value);
});

Отличия от Selectize.js

Несмотря на высокий уровень совместимости, существуют различия.

Изменения в рендеринге

Некоторые HTML-структуры отличаются:

  • изменены CSS-классы;
  • изменён DOM dropdown;
  • переработаны внутренние контейнеры;
  • изменено поведение некоторых плагинов.

Старые CSS-правила могут перестать работать.

Пример проблемного селектора:

.selectize-dropdown-content {
    max-height: 300px;
}

В Tom Select структура может отличаться в зависимости от плагина и темы.


Совместимость CSS

Проблемы старых тем

Многие старые темы были написаны под:

  • Selectize.js;
  • Select2;
  • Chosen;
  • Bootstrap 3.

Tom Select использует собственные классы:

<div class="ts-wrapper">

Если старый код ожидает:

<div class="selectize-control">

стили могут не применяться.


Добавление legacy-классов

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

const control = new TomSelect('#tags');

control.wrapper.classList.add('selectize-control');
control.dropdown.classList.add('selectize-dropdown');

Это позволяет временно сохранить старую CSS-базу.


Совместимость с Bootstrap

Tom Select не зависит от Bootstrap, но может использоваться вместе с ним.

Bootstrap 3

Основные проблемы:

  • старые reset-стили;
  • z-index конфликтов;
  • несовместимые размеры input;
  • проблемы flex-layout.

Иногда требуется адаптация:

.ts-wrapper.form-control {
    height: auto;
}

Bootstrap 4 и 5

Совместимость значительно лучше.

Пример:

new TomSelect('#city', {
    create: false
});
<select class="form-select" id="city">

Совместимость с React

Tom Select не является React-компонентом, но может использоваться внутри React-приложений.

Основная проблема

Tom Select напрямую изменяет DOM, тогда как React ожидает полный контроль над ним.

Неправильный подход:

<select value={value}>

Tom Select начнёт конфликтовать с virtual DOM.


Правильная интеграция

Обычно используется ref:

const selectRef = useRef();

useEffect(() => {
    const control = new TomSelect(selectRef.current);

    return () => control.destroy();
}, []);

Совместимость с Vue

Во Vue возникают аналогичные проблемы.

Правильный подход:

mounted() {
    this.control = new TomSelect(this.$refs.select);
},
beforeUnmount() {
    this.control.destroy();
}

Совместимость с Angular

Angular также требует ручного контроля жизненного цикла.

Пример:

ngAfterViewInit() {
    this.control = new TomSelect(this.select.nativeElement);
}

ngOnDestroy() {
    this.control.destroy();
}

Совместимость с AJAX-кодом

Многие старые проекты используют jQuery AJAX:

$.get('/users', data => {
    control.addOptions(data);
});

Tom Select полностью совместим с таким подходом.

Также поддерживается современный fetch.


Совместимость со старыми браузерами

Tom Select ориентирован на современные браузеры.

Возможные проблемы

В старых браузерах могут отсутствовать:

  • Promise;
  • fetch;
  • classList;
  • CustomEvent;
  • Array.fr om.

Для поддержки legacy-среды требуются polyfill.


Polyfill для Promise

<script src="https://cdn.jsdelivr.net/npm/promise-polyfill/dist/polyfill.min.js"></script>

Polyfill для fetch

<script src="https://cdn.jsdelivr.net/npm/whatwg-fetch/dist/fetch.umd.js"></script>

Совместимость с Internet Explorer

Полноценная поддержка IE отсутствует.

Проблемы:

  • современные DOM API;
  • ES6-синтаксис;
  • обработка событий;
  • производительность;
  • проблемы CSS Grid/Flexbox.

Для старых корпоративных систем иногда сохраняют Selectize.js вместо миграции.


Частичная миграция

В крупных проектах часто невозможно сразу заменить все компоненты.

Гибридный подход

Некоторые формы продолжают использовать старую библиотеку:

if (window.useLegacySelectize) {
    $('#users').selectize();
} else {
    new TomSelect('#users');
}

Это позволяет выполнять постепенный переход.


Адаптер совместимости

В больших системах полезно создать единый слой абстракции.

Пример:

class SelectAdapter {
    constructor(selector, options) {
        this.instance = new TomSelect(selector, options);
    }

    clear() {
        this.instance.clear();
    }

    setValue(value) {
        this.instance.setValue(value);
    }

    getValue() {
        return this.instance.getValue();
    }
}

Теперь приложение зависит не от конкретной библиотеки, а от адаптера.


Стабилизация публичного API

При миграции важно избегать прямого обращения к внутренним свойствам:

Плохо:

control.dropdown_content.innerHTML = '';

Хорошо:

control.clearOptions();

Внутренние свойства могут измениться между версиями.


Совместимость плагинов

Плагины Selectize.js не всегда работают без изменений.

Основные причины

  • изменения внутреннего DOM;
  • обновлённый event lifecycle;
  • изменения API;
  • различия в хуках;
  • переработка рендеринга.

Проверка старого плагина

Старый код:

TomSelect.define('legacy_plugin', function() {
    this.on('initialize', () => {
        console.log('init');
    });
});

Может работать без изменений, если не использует внутренние свойства.


Проблемы внутренних API

Наиболее опасная зона миграции — использование приватных методов:

control.setupTemplates();

или:

control.positionDropdown();

Подобные методы могут изменяться между версиями.


Стратегия безопасной миграции

Этап 1. Изоляция

Сначала выделяется весь код работы с select-компонентами.

Этап 2. Адаптер

Создаётся совместимый слой API.

Этап 3. Постепенная замена

Отдельные страницы переводятся на Tom Select.

Этап 4. Удаление legacy-кода

После полной миграции убираются:

  • alias;
  • polyfill;
  • старые классы;
  • jQuery-адаптеры;
  • fallback-режимы.

Поддержка старых событий

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

select.addEventListener('change', handler);

Tom Select синхронизирует состояние с оригинальным <select>, поэтому такие обработчики продолжают работать.


Совместимость с MutationObserver

Во многих старых системах DOM меняется динамически.

Например:

select.innerHTML = html;

Tom Select не всегда автоматически обнаруживает такие изменения.

Требуется ручной вызов:

control.sync();

Совместимость с динамическим HTML

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

Проверка:

if (!element.tomselect) {
    new TomSelect(element);
}

Без такой проверки возникают:

  • дублирование dropdown;
  • утечки памяти;
  • повторные события;
  • повреждение DOM.

Совместимость с SSR

При серверном рендеринге важно учитывать отсутствие DOM на сервере.

Неправильно:

new TomSelect('#users');

в SSR-контексте.

Правильно:

if (typeof window !== 'undefined') {
    new TomSelect('#users');
}

Версионная совместимость

При обновлении Tom Select между версиями необходимо проверять:

  • changelog;
  • deprecated API;
  • изменения DOM;
  • изменения CSS-классов;
  • изменения событий.

Обратная совместимость собственных компонентов

Во многих проектах поверх Selectize.js уже существует внутренний компонент:

createUserSelect(selector);

Лучший путь миграции — изменить внутреннюю реализацию:

function createUserSelect(selector) {
    return new TomSelect(selector);
}

При этом остальная система не меняется.


Feature detection вместо version detection

Плохо:

if (TomSelect.version === '2.1') {

Хорошо:

if (typeof control.sync === 'function') {

Такой подход устойчивее к обновлениям.


Обратная совместимость конфигурации

Старые конфиги можно автоматически преобразовывать.

Пример адаптации:

function normalizeConfig(config) {
    if (config.max_items) {
        config.maxItems = config.max_items;
    }

    return config;
}

Совместимость с legacy backend

Старые серверы могут ожидать строку:

1,2,3

вместо массива.

Tom Select позволяет настраивать сериализацию:

control.getValue();

или:

control.getValue().join(',');

Тестирование совместимости

При миграции особенно важно проверять:

  • keyboard navigation;
  • mobile UI;
  • form submit;
  • accessibility;
  • reset формы;
  • destroy/init циклы;
  • динамическое обновление options;
  • работу старых CSS.

Типичные ошибки миграции

Повторная инициализация

new TomSelect('#users');
new TomSelect('#users');

Использование внутренних свойств

control.$dropdown

Зависимость от DOM-структуры

querySelector('.item:last-child')

Отсутствие destroy()

control.destroy();

Смешивание React state и DOM-манипуляций

value={state}

одновременно с ручным setValue().


Минимизация риска при обновлениях

Для долгосрочной стабильности рекомендуется:

  • использовать только публичный API;
  • избегать DOM-hack;
  • изолировать библиотеку адаптером;
  • не зависеть от внутренних CSS-классов;
  • тестировать обновления в staging;
  • фиксировать версии пакетов;
  • поддерживать backward-compatible wrappers.