Включение режима создания

Режим создания элементов позволяет добавлять новые значения, которых нет в исходном списке <option> или в массиве данных. Эта возможность особенно полезна при разработке систем тегов, форм ввода категорий, email-адресов, навыков, ключевых слов и других динамических интерфейсов.

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


Базовое включение режима создания

Для активации используется параметр create.

<select id="skills" multiple>
  <option value="js">JavaScript</option>
  <option value="php">PHP</option>
</select>
new TomSelect('#skills', {
    create: true
});

После включения:

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

Как работает создание элемента

Когда пользователь вводит текст:

React

Tom Select:

  1. Проверяет наличие совпадения среди опций.

  2. Если совпадения нет — предлагает создать новый элемент.

  3. После подтверждения:

    • создаёт объект option;
    • добавляет его в список;
    • выбирает его как активный элемент.

Внешний вид кнопки создания

При включённом create: true интерфейс автоматически показывает строку:

Add React...

Текст зависит от введённого значения.


Создание элемента по нажатию Enter

Основной сценарий:

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

Поведение:

  • ввод текста;
  • нажатие Enter;
  • создание нового элемента;
  • очистка поля ввода.

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

Наиболее распространённый вариант — режим множественного выбора.

<select id="tags" multiple></select>
new TomSelect('#tags', {
    create: true
});

Теперь можно добавлять любое количество пользовательских значений.


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

Tom Select может работать не только с <select>, но и с <input>.

<input id="emails">
new TomSelect('#emails', {
    create: true,
    persist: false
});

В этом режиме значения хранятся как элементы коллекции.


Создание объекта через функцию

Вместо true можно передать функцию.

Это позволяет полностью контролировать структуру создаваемых объектов.

new TomSelect('#users', {
    create: function(input) {
        return {
            value: input.toLowerCase(),
            text: input
        };
    }
});

Аргумент input

Функция получает введённую строку.

create: function(input) {
    console.log(input);
}

Если пользователь ввёл:

Vue

то в input попадёт:

"Vue"

Возвращаемый объект

Функция должна вернуть объект option.

Минимальный вариант:

return {
    value: input,
    text: input
};

Дополнительные поля

Можно добавлять собственные свойства.

new TomSelect('#tags', {
    create: function(input) {
        return {
            value: input,
            text: input,
            custom: true,
            createdAt: Date.now()
        };
    }
});

Эти поля затем доступны:

  • в рендеринге;
  • фильтрации;
  • обработчиках;
  • API Tom Select.

Генерация уникальных идентификаторов

Часто требуется создавать ID автоматически.

new TomSelect('#items', {
    create: function(input) {
        return {
            id: crypto.randomUUID(),
            value: input,
            text: input
        };
    },
    valueField: 'value',
    labelField: 'text'
});

Преобразование регистра

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

Приведение к нижнему регистру

create: function(input) {
    return {
        value: input.toLowerCase(),
        text: input
    };
}

Приведение к верхнему регистру

create: function(input) {
    return {
        value: input.toUpperCase(),
        text: input
    };
}

Удаление лишних пробелов

create: function(input) {
    input = input.trim();

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

Запрет создания пустых значений

new TomSelect('#tags', {
    create: function(input) {

        input = input.trim();

        if (!input.length) {
            return false;
        }

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

Возврат false отменяет создание.


Проверка минимальной длины

new TomSelect('#tags', {
    create: function(input) {

        if (input.length < 3) {
            return false;
        }

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

Проверка через регулярные выражения

Разрешение только латиницы

new TomSelect('#tags', {
    create: function(input) {

        if (!/^[a-z]+$/i.test(input)) {
            return false;
        }

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

Создание email-адресов

new TomSelect('#emails', {
    create: function(input) {

        const valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

        if (!valid.test(input)) {
            return false;
        }

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

Создание тегов с автоматическим slug

new TomSelect('#tags', {
    create: function(input) {

        const slug = input
            .toLowerCase()
            .replace(/\s+/g, '-');

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

Связь create и persist

Параметр persist определяет, сохраняются ли созданные элементы в списке опций.

persist: true

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

Поведение:

  • созданные элементы остаются в options;
  • доступны при повторном открытии списка.

persist: false

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

Поведение:

  • после удаления выбранного элемента он исчезает полностью;
  • временные значения не сохраняются.

Это особенно полезно для одноразовых тегов.


createOnBlur

Позволяет создавать элемент автоматически при потере фокуса.

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

Теперь необязательно нажимать Enter.


createFilter

Позволяет определить, какие значения разрешено создавать.

Фильтр через RegExp

new TomSelect('#tags', {
    create: true,
    createFilter: /^[A-Za-z0-9]+$/
});

Фильтр через функцию

new TomSelect('#tags', {
    create: true,
    createFilter: function(input) {
        return input.length >= 3;
    }
});

Отличие createFilter от create

createFilter

Только разрешает или запрещает создание.

create

Создаёт сам объект option.

Обычно они работают совместно.

new TomSelect('#tags', {
    createFilter: input => input.length >= 3,

    create: function(input) {
        return {
            value: input,
            text: input
        };
    }
});

createFilter и защита от дублей

new TomSelect('#tags', {
    create: true,

    createFilter: function(input) {

        input = input.toLowerCase();

        return !this.options.hasOwnProperty(input);
    }
});

Изменение текста кнопки создания

Для настройки используется render.option_create.

new TomSelect('#tags', {

    create: true,

    render: {
        option_create: function(data, escape) {
            return `
                <div class="create">
                    Создать тег:
                    <strong>${escape(data.input)}</strong>
                </div>
            `;
        }
    }
});

Экранирование данных

В render-функциях необходимо использовать escape.

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

return `<div>${data.input}</div>`;

Правильно:

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

Это предотвращает XSS-атаки.


Автоматическое создание при разделителях

Часто используется совместно с delimiter.

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

Теперь ввод:

html,css,javascript

автоматически создаёт несколько элементов.


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

Создание можно совмещать с AJAX-загрузкой.

new TomSelect('#users', {

    valueField: 'id',
    labelField: 'name',
    searchField: 'name',

    create: true,

    load: function(query, callback) {

        fetch('/api/users?q=' + query)
            .then(response => response.json())
            .then(data => callback(data))
            .catch(() => callback());
    }
});

Создание и серверная синхронизация

Нередко новый элемент должен сохраняться на сервере.

new TomSelect('#categories', {

    create: function(input, callback) {

        fetch('/api/categories', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                title: input
            })
        })
        .then(response => response.json())
        .then(category => {

            callback({
                value: category.id,
                text: category.title
            });

        })
        .catch(() => callback());
    }
});

Асинхронное создание

Tom Select поддерживает callback-механику.

create: function(input, callback) {

    setTimeout(() => {

        callback({
            value: input,
            text: input
        });

    }, 1000);
}

Отмена асинхронного создания

create: function(input, callback) {

    callback();
}

или:

create: function(input, callback) {
    callback(false);
}

Взаимодействие с пользовательским рендерингом

Созданные элементы можно визуально выделять.

new TomSelect('#tags', {

    create: function(input) {
        return {
            value: input,
            text: input,
            userCreated: true
        };
    },

    render: {

        option: function(data, escape) {

            const cls = data.userCreated
                ? 'created'
                : 'default';

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

Доступ к созданным элементам через API

После создания объект появляется в options.

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

console.log(select.options);

Добавление созданного элемента вручную

select.addOption({
    value: 'nodejs',
    text: 'Node.js'
});

Немедленный выбор нового элемента

select.addItem('nodejs');

Создание и добавление одновременно

select.addOption({
    value: 'react',
    text: 'React'
});

select.addItem('react');

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

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

Отключение создания после достижения лимита

new TomSelect('#tags', {

    create: function(input) {

        if (this.items.length >= 5) {
            return false;
        }

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

Использование create совместно с plugins

remove_button

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

drag_drop

new TomSelect('#tags', {
    plugins: ['drag_drop'],
    create: true
});

Частые ошибки

Возврат строки вместо объекта

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

create: function(input) {
    return input;
}

Правильно:

create: function(input) {
    return {
        value: input,
        text: input
    };
}

Отсутствие valueField

Если структура данных нестандартная:

{
    id: 1,
    title: 'Vue'
}

необходимо указать:

new TomSelect('#tags', {
    valueField: 'id',
    labelField: 'title'
});

Неэкранированный HTML

Опасно:

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

Безопасно:

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

Практический пример системы тегов

<input id="article-tags">
new TomSelect('#article-tags', {

    plugins: ['remove_button'],

    persist: false,

    createOnBlur: true,

    delimiter: ',',

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

    create: function(input) {

        input = input.trim();

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

    render: {

        option_create: function(data, escape) {

            return `
                <div class="create-tag">
                    Новый тег:
                    <strong>${escape(data.input)}</strong>
                </div>
            `;
        }
    }
});