Ограничение количества выборов

Механизм множественного выбора на базе Awesomplete обычно реализуется через разбиение значения input-поля на токены и хранение выбранных элементов в отдельной структуре данных. Ограничение количества таких выборов вводится на уровне логики обработки события выбора, а не внутри самой библиотеки, поскольку Awesomplete не содержит встроенного параметра maxItems.

Ключевая идея заключается в синхронизации трёх компонентов:

  • текстового поля ввода
  • массива выбранных значений
  • обработчика события выбора элемента

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


Базовый механизм подсчёта выбранных значений

Для хранения выбранных элементов используется массив, который синхронизируется с визуальным содержимым input.

const input = document.querySelector("#tags");
const awesomplete = new Awesomplete(input, {
    list: ["JavaScript", "TypeScript", "Python", "Go", "Rust", "Java"]
});

const selected = [];
const MAX_SELECTIONS = 3;

Каждое подтверждение выбора через Awesomplete фиксируется событием awesomplete-selectcomplete.

input.addEventListener("awesomplete-selectcomplete", function (event) {
    const value = event.text.value;
});

В этот момент появляется точка контроля: добавление значения либо разрешается, либо блокируется.


Перехват выбора и контроль лимита

Основной механизм ограничения реализуется через проверку длины массива selected.

input.addEventListener("awesomplete-selectcomplete", function (event) {
    const value = event.text.value;

    if (selected.length >= MAX_SELECTIONS) {
        event.preventDefault?.();
        input.value = selected.join(", ");
        return;
    }

    if (!selected.includes(value)) {
        selected.push(value);
    }

    input.value = selected.join(", ");
});

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


Разделение значений и работа с токенами

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

function parseInput(value) {
    return value
        .split(",")
        .map(item => item.trim())
        .filter(Boolean);
}

При необходимости пересборки строки используется обратная операция:

function formatOutput(arr) {
    return arr.join(", ");
}

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


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

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

input.addEventListener("awesomplete-open", function () {
    if (selected.length >= MAX_SELECTIONS) {
        awesomplete.close();
    }
});

Альтернативный подход заключается в динамическом изменении источника данных:

function getFilteredList() {
    if (selected.length >= MAX_SELECTIONS) {
        return [];
    }
    return ["JavaScript", "TypeScript", "Python", "Go", "Rust", "Java"]
        .filter(item => !selected.includes(item));
}

awesomplete.list = getFilteredList();

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


Обновление состояния при ручном вводе

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

input.addEventListener("blur", function () {
    const values = parseInput(input.value);

    selected.length = 0;

    for (let i = 0; i < values.length; i++) {
        if (selected.length < MAX_SELECTIONS) {
            selected.push(values[i]);
        }
    }

    input.value = formatOutput(selected);
});

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


Предотвращение дублирования значений

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

input.addEventListener("awesomplete-selectcomplete", function (event) {
    const value = event.text.value;

    if (selected.includes(value)) {
        input.value = formatOutput(selected);
        return;
    }

    if (selected.length < MAX_SELECTIONS) {
        selected.push(value);
    }

    input.value = formatOutput(selected);
});

Такой подход предотвращает рост массива за счёт повторных кликов по одному и тому же элементу списка.


Управление состоянием ввода при достижении лимита

При заполнении максимального количества значений логика интерфейса обычно переводится в режим ограничения ввода. Это достигается через изменение состояния поля:

function updateInputState() {
    if (selected.length >= MAX_SELECTIONS) {
        input.setAttribute("disabled", "true");
    } else {
        input.removeAttribute("disabled");
    }
}

Вызывается после каждого изменения массива:

function sync(value) {
    input.value = formatOutput(value);
    updateInputState();
}

Очистка и перерасчёт состояния

Удаление элементов из списка требует обратной синхронизации между строкой и массивом.

function removeItem(item) {
    const index = selected.indexOf(item);

    if (index !== -1) {
        selected.splice(index, 1);
    }

    sync(selected);
}

При наличии UI-элементов (например, тегов) удаление обычно связано с обработчиком клика по конкретному элементу, что позволяет динамически снижать количество выбранных значений и снова открывать возможность добавления новых.


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

Сложные сценарии используют комбинацию фильтрации списка и ограничения выбора. В этом случае Awesomplete выступает только как слой автодополнения, а логика контроля полностью переносится в пользовательский код.

function canAddMore() {
    return selected.length < MAX_SELECTIONS;
}

function getList() {
    return ["JavaScript", "TypeScript", "Python", "Go", "Rust", "Java"]
        .filter(item => !selected.includes(item));
}

input.addEventListener("input", function () {
    awesomplete.list = getList();
});

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


Обработка состояния при программных изменениях значения

Изменение значения input через код требует ручного пересчёта состояния, поскольку Awesomplete не отслеживает внешние изменения как часть внутреннего состояния.

function setValues(values) {
    selected.length = 0;

    for (const v of values) {
        if (selected.length < MAX_SELECTIONS) {
            selected.push(v);
        }
    }

    input.value = formatOutput(selected);
    updateInputState();
}

Такая функция становится точкой входа для инициализации данных и синхронизации внешних источников с внутренним состоянием компонента.


Согласованность данных между UI и состоянием

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

  • визуальным содержимым input
  • массивом выбранных значений
  • состоянием выпадающего списка Awesomplete

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

  • проверка лимита
  • фильтрация значений
  • обновление input
  • обновление списка автодополнения