Миграция с Select2

Переход с Select2 на Tom Select затрагивает не только API, но и сам подход к построению компонента выбора. Несмотря на внешнюю схожесть, библиотеки имеют различную внутреннюю структуру, систему событий, модель расширения и принципы работы с DOM.

Основные различия

Возможность Select2 Tom Select
Зависимость от jQuery Требуется Не требуется
Размер библиотеки Больше Легче
Архитектура jQuery-плагин Нативный ES6-класс
Плагины Ограниченная система Гибкая plugin API
Работа с данными jQuery-ориентированная Объектная модель
Поддержка TypeScript Ограниченная Более удобная
Кастомный рендеринг Через шаблоны Через render callbacks
Производительность Ниже на больших списках Выше

Tom Select появился как современное развитие идей Selectize.js и ориентирован на отказ от jQuery, улучшенную производительность и более чистую архитектуру.


Удаление зависимости от jQuery

Одно из главных изменений при миграции — отказ от jQuery.

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

$('#users').select2({
    placeholder: 'Выберите пользователя'
});

Инициализация Tom Select

new TomSelect('#users', {
    placeholder: 'Выберите пользователя'
});

Tom Select использует обычные CSS-селекторы и работает напрямую с DOM.


Подключение библиотеки

Select2

<link rel="stylesheet" href="select2.min.css">
<script src="jquery.min.js"></script>
<script src="select2.min.js"></script>

Tom Select

<link rel="stylesheet" href="tom-select.css">
<script src="tom-select.complete.min.js"></script>

В Tom Select отсутствует обязательная зависимость от jQuery, что значительно уменьшает общий размер frontend-бандла.


Замена базовой инициализации

Select2

$('.tags').select2();

Tom Select

document.querySelectorAll('.tags').forEach((element) => {
    new TomSelect(element);
});

В Tom Select экземпляр создаётся через конструктор класса.


Получение экземпляра компонента

Select2

const instance = $('#users').data('select2');

Tom Select

const select = new TomSelect('#users');

console.log(select);

Экземпляр хранится напрямую в переменной.

Также доступен через DOM:

const control = document.querySelector('#users').tomselect;

Работа со значениями

Получение значения

Select2

const value = $('#users').val();

Tom Select

const value = select.getValue();

Установка значения

Select2

$('#users').val('admin').trigger('change');

Tom Select

select.setValue('admin');

Tom Select автоматически инициирует необходимые обновления интерфейса.


Множественный выбор

Select2

<select id="skills" multiple>
$('#skills').select2();

Tom Select

<select id="skills" multiple>
new TomSelect('#skills');

Базовая логика работы остаётся схожей.


Placeholder

Select2

$('#country').select2({
    placeholder: 'Страна'
});

Tom Select

new TomSelect('#country', {
    placeholder: 'Страна'
});

Однако в Tom Select placeholder отображается иначе при multiple-режиме и зависит от состояния поля.


Работа с AJAX

Select2 AJAX API

$('#users').select2({
    ajax: {
        url: '/api/users',
        dataType: 'json',
        delay: 250,
        processResults: function(data) {
            return {
                results: data.items
            };
        }
    }
});

Tom Select async loading

new TomSelect('#users', {
    load: function(query, callback) {

        fetch(`/api/users?q=${query}`)
            .then(response => response.json())
            .then(data => {
                callback(data.items);
            })
            .catch(() => {
                callback();
            });

    }
});

Отличия подходов

Select2 Tom Select
Конфигурация через ajax Используется load
Встроенный transport Используется fetch/XHR вручную
processResults callback(data)
jQuery AJAX Любой HTTP-клиент

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


Формат данных

Select2

{
    "results": [
        {
            "id": 1,
            "text": "Admin"
        }
    ]
}

Tom Select

[
    {
        "value": 1,
        "text": "Admin"
    }
]

Tom Select использует более простую структуру.


Настройка полей

Если backend возвращает структуру Select2, можно настроить поля.

new TomSelect('#users', {
    valueField: 'id',
    labelField: 'text',
    searchField: 'text',

    load: function(query, callback) {

        fetch('/api/users')
            .then(r => r.json())
            .then(data => {
                callback(data.results);
            });

    }
});

Это позволяет не менять серверный API во время миграции.


События

Select2 events

$('#users').on('select2:select', function(e) {
    console.log(e.params.data);
});

Tom Select events

select.on('item_add', function(value) {
    console.log(value);
});

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

Select2 Tom Select
select2:select item_add
select2:unselect item_remove
change change
select2:open dropdown_open
select2:close dropdown_close

Передача данных события

Select2

$('#users').on('select2:select', function(e) {
    console.log(e.params.data);
});

Tom Select

select.on('item_add', function(value, item) {

    console.log(value);
    console.log(item);

});

Tom Select передаёт аргументы напрямую, без обёртки event object.


Уничтожение компонента

Select2

$('#users').select2('destroy');

Tom Select

select.destroy();

После destroy() Tom Select полностью удаляет собственные обработчики и DOM-модификации.


Динамическое добавление options

Select2

$('#users').append(new Option('Admin', 1));

Tom Select

select.addOption({
    value: 1,
    text: 'Admin'
});

select.refreshOptions(false);

Добавление выбранного элемента

Select2

$('#users').val(1).trigger('change');

Tom Select

select.addItem(1);

Удаление элемента

Tom Select

select.removeItem(1);

Очистка выбора

Select2

$('#users').val(null).trigger('change');

Tom Select

select.clear();

Кастомный рендеринг

Select2 templateResult

$('#users').select2({

    templateResult: function(user) {

        return $(`
            <div>
                <strong>${user.text}</strong>
            </div>
        `);

    }

});

Tom Select render.option

new TomSelect('#users', {

    render: {

        option: function(data, escape) {

            return `
                <div>
                    <strong>${escape(data.text)}</strong>
                </div>
            `;

        }

    }

});

Экранирование HTML

В Select2 экранирование часто делалось вручную или через jQuery.

Tom Select предоставляет встроенную функцию escape.

render: {

    option: function(data, escape) {

        return `
            <div>${escape(data.text)}</div>
        `;

    }

}

Это особенно важно при отображении данных из API.


Создание новых элементов

Select2 tags

$('#tags').select2({
    tags: true
});

Tom Select create

new TomSelect('#tags', {
    create: true
});

Кастомное создание

new TomSelect('#tags', {

    create: function(input) {

        return {
            value: input,
            text: input
        };

    }

});

Поиск

Select2 matcher

matcher: function(params, data) {
    return data;
}

searchField: ['name', 'email']

Tom Select делает акцент на конфигурации поисковых полей, а не на полном переопределении matcher.


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

Select2

maximumSelectionLength: 3

Tom Select

maxItems: 3

Отключение поля

Select2

$('#users').prop('disabled', true);

Tom Select

select.disable();

Включение обратно:

select.enable();

Работа с optgroup

Select2

<optgroup label="Backend">
    <option>PHP</option>
</optgroup>

Tom Select

Поддержка optgroup сохраняется.

Также можно работать через JS-конфигурацию:

new TomSelect('#skills', {

    optgroups: [
        {
            value: 'backend',
            label: 'Backend'
        }
    ],

    options: [
        {
            value: 'php',
            text: 'PHP',
            optgroup: 'backend'
        }
    ]

});

Плагины

Select2 использует ограниченную систему расширения.

Tom Select поддерживает полноценные plugins.

Пример подключения

new TomSelect('#tags', {
    plugins: ['remove_button']
});

Аналоги возможностей Select2

Select2 Tom Select
tags create
tokenSeparators delimiter
maximumSelectionLength maxItems
templateResult render.option
templateSelection render.item
ajax load
matcher searchField/custom scoring
placeholder placeholder

Token separators

Select2

tokenSeparators: [',']

Tom Select

delimiter: ','

Загрузка данных при открытии

Select2

minimumInputLength: 0

Tom Select

preload: true

Ленивая загрузка

new TomSelect('#users', {

    preload: 'focus',

    load: function(query, callback) {

        fetch('/api/users')
            .then(r => r.json())
            .then(data => callback(data));

    }

});

Работа с CSS

Select2

Select2 генерирует сложную структуру классов:

.select2-container
.select2-selection
.select2-results

Tom Select

Tom Select использует более простую структуру:

.ts-wrapper
.ts-control
.ts-dropdown

Особенности миграции CSS

Наиболее частая проблема при переходе — старые стили Select2 продолжают влиять на новый компонент.

Рекомендуется:

.select2-container {
    all: unset;
}

или полное удаление Select2 CSS.


Замена theme API

Select2

theme: 'classic'

Tom Select

Tom Select использует обычные CSS-файлы тем.

<link rel="stylesheet" href="tom-select.bootstrap5.css">

Bootstrap-интеграция

Select2

Часто требовались сторонние темы.

Tom Select

Доступны готовые варианты:

tom-select.bootstrap4.css
tom-select.bootstrap5.css

Работа с form submit

Tom Select синхронизирует состояние с исходным select автоматически.

<select name="users[]" multiple>

При отправке формы данные отправляются стандартным образом.


Инициализация из массива

Select2

$('#users').select2({
    data: users
});

Tom Select

new TomSelect('#users', {
    options: users
});

Изменение текста option

Tom Select

select.updateOption(1, {
    value: 1,
    text: 'Administrator'
});

Полное обновление options

select.clearOptions();

select.addOptions([
    { value: 1, text: 'Admin' },
    { value: 2, text: 'Editor' }
]);

Производительность на больших списках

Tom Select работает быстрее благодаря:

  • отсутствию jQuery;
  • меньшему количеству DOM-операций;
  • более компактному рендерингу;
  • оптимизированному поиску;
  • современной архитектуре.

Особенно заметна разница при:

  • 5000+ options;
  • async search;
  • multiple select;
  • частом обновлении данных.

Проблемы совместимости

jQuery-код больше не работает

Было

$('#users').select2('open');

Стало

select.open();

Отсутствует processResults

В Tom Select данные должны приводиться вручную.

load(query, callback) {

    fetch('/api/users')
        .then(r => r.json())
        .then(data => {

            callback(
                data.results.map(item => ({
                    value: item.id,
                    text: item.text
                }))
            );

        });

}

Различия в DOM

Select2 создаёт большое количество вложенных элементов.

Tom Select использует более компактную структуру.

Это влияет на:

  • CSS-селекторы;
  • тесты;
  • автотесты Selenium;
  • пользовательские темы.

Миграция legacy-кода

Этап 1 — удаление jQuery API

Было

$('#users').val(1).trigger('change');

Стало

select.setValue(1);

Этап 2 — перенос AJAX

Было

ajax: {
    url: '/api/users'
}

Стало

load(query, callback) {

    fetch('/api/users')
        .then(r => r.json())
        .then(callback);

}

Этап 3 — перенос шаблонов

Было

templateResult

Стало

render.option

Этап 4 — обновление CSS

Удаляются:

  • .select2-container
  • .select2-selection
  • .select2-results

Добавляются:

  • .ts-wrapper
  • .ts-control
  • .ts-dropdown

Типичная стратегия миграции

Параллельная поддержка

Иногда обе библиотеки работают одновременно.

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

Постепенная миграция

Часто сначала переводятся:

  1. простые select;
  2. multiple select;
  3. async search;
  4. кастомные шаблоны;
  5. сложные плагины.

Проверка совместимости backend API

Наиболее частые несовместимости:

Проблема Решение
results вместо массива callback(data.results)
id/text поля valueField/labelField
jQuery AJAX headers fetch headers
pagination Select2 собственная логика

Миграция сложного Select2-конфига

Select2

$('#users').select2({

    placeholder: 'Пользователь',

    ajax: {
        url: '/api/users',
        dataType: 'json'
    },

    templateResult: formatUser,
    minimumInputLength: 2,
    maximumSelectionLength: 5,
    tags: true

});

Tom Select

new TomSelect('#users', {

    placeholder: 'Пользователь',

    maxItems: 5,

    create: true,

    preload: false,

    load: function(query, callback) {

        if (query.length < 2) {
            return callback();
        }

        fetch(`/api/users?q=${query}`)
            .then(r => r.json())
            .then(data => callback(data.results))
            .catch(() => callback());

    },

    render: {

        option: function(data, escape) {

            return `
                <div>
                    ${escape(data.text)}
                </div>
            `;

        }

    }

});