Работа с localStorage

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


Базовая модель интеграции Awesomplete и localStorage

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

const input = document.querySelector("#search");

const stored = localStorage.getItem("awesomplete-data");
const list = stored ? JSON.parse(stored) : [];

const awesomplete = new Awesomplete(input, {
    list: list
});

На данном этапе реализуется простая схема: при инициализации компонента данные берутся из локального хранилища и передаются в Awesomplete.


Сохранение пользовательского ввода в localStorage

Частый сценарий — накопление истории запросов пользователя. Это позволяет формировать персонализированные подсказки.

function saveToStorage(value) {
    const key = "awesomplete-data";
    const stored = localStorage.getItem(key);

    let list = stored ? JSON.parse(stored) : [];

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

    localStorage.setItem(key, JSON.stringify(list));
}

Функция выполняет три ключевые операции:

  • извлечение текущего массива;
  • проверка на дублирование;
  • сохранение обновлённого массива обратно в localStorage.

Привязка сохранения к событию выбора элемента

Awesomplete предоставляет событие awesomplete-selectcomplete, которое срабатывает при выборе подсказки. Это оптимальная точка для записи данных.

input.addEventListener("awesomplete-selectcomplete", function () {
    saveToStorage(this.value);
});

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


Ограничение размера хранилища

localStorage имеет ограничение по объёму (обычно около 5–10 МБ), поэтому хранение неограниченного массива недопустимо. Необходимо вводить ограничение длины списка.

function saveToStorage(value) {
    const key = "awesomplete-data";
    const stored = localStorage.getItem(key);

    let list = stored ? JSON.parse(stored) : [];

    if (!list.includes(value)) {
        list.unshift(value);
    }

    if (list.length > 20) {
        list = list.slice(0, 20);
    }

    localStorage.setItem(key, JSON.stringify(list));
}

Использование unshift позволяет сохранять наиболее свежие значения в начале списка, что удобно для автодополнения.


Динамическое обновление списка Awesomplete

Awesomplete не всегда автоматически реагирует на изменения массива, поэтому требуется явное обновление list.

function updateAwesomplete(list) {
    awesomplete.list = list;
}

И интеграция с сохранением:

function saveToStorage(value) {
    const key = "awesomplete-data";
    const stored = localStorage.getItem(key);

    let list = stored ? JSON.parse(stored) : [];

    if (!list.includes(value)) {
        list.unshift(value);
    }

    if (list.length > 20) {
        list = list.slice(0, 20);
    }

    localStorage.setItem(key, JSON.stringify(list));
    updateAwesomplete(list);
}

Таким образом достигается синхронизация между UI-компонентом и локальным состоянием.


Очистка данных localStorage

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

function clearAwesompleteStorage() {
    localStorage.removeItem("awesomplete-data");
    awesomplete.list = [];
}

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


Разделение хранилищ по контексту

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

function getStorageKey(context) {
    return `awesomplete-${context}`;
}

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

function saveToStorage(value, context) {
    const key = getStorageKey(context);
    const stored = localStorage.getItem(key);

    let list = stored ? JSON.parse(stored) : [];

    if (!list.includes(value)) {
        list.unshift(value);
    }

    localStorage.setItem(key, JSON.stringify(list));
}

Это предотвращает смешивание данных разных сценариев.


Работа с объектными данными

Awesomplete поддерживает не только строки, но и объекты вида { label, value }. При использовании localStorage требуется учитывать сериализацию.

const item = { label: "JavaScript", value: "js" };
localStorage.setItem("awesomplete-data", JSON.stringify([item]));

При восстановлении:

const stored = JSON.parse(localStorage.getItem("awesomplete-data"));

const awesomplete = new Awesomplete(input, {
    list: stored
});

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


Фильтрация данных перед сохранением

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

function normalize(value) {
    return value.trim().toLowerCase();
}

Интеграция:

function saveToStorage(value) {
    const key = "awesomplete-data";
    const normalized = normalize(value);

    const stored = localStorage.getItem(key);
    let list = stored ? JSON.parse(stored) : [];

    const normalizedList = list.map(normalize);

    if (!normalizedList.includes(normalized)) {
        list.unshift(value);
    }

    localStorage.setItem(key, JSON.stringify(list));
}

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


Ленивое чтение localStorage

При больших объёмах данных целесообразно откладывать чтение localStorage до момента фокусировки на поле ввода.

let awesomplete;

input.addEventListener("focus", function () {
    if (!awesomplete) {
        const stored = localStorage.getItem("awesomplete-data");
        const list = stored ? JSON.parse(stored) : [];

        awesomplete = new Awesomplete(input, {
            list: list
        });
    }
});

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


Обработка ошибок JSON

localStorage хранит данные в виде строк, поэтому возможны ошибки при парсинге.

function safeParse(data) {
    try {
        return JSON.parse(data);
    } catch (e) {
        return [];
    }
}

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

const stored = safeParse(localStorage.getItem("awesomplete-data"));

Это предотвращает поломку логики автодополнения при повреждённых данных.


Версионирование структуры данных

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

const STORAGE_VERSION = "v1";

function getKey(context) {
    return `awesomplete-${context}-${STORAGE_VERSION}`;
}

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


Сжатие данных перед сохранением

При большом количестве записей можно уменьшить объём хранения за счёт упрощения структуры.

const list = ["javascript", "python", "go"];

localStorage.setItem("awesomplete-data", list.join("|"));

Восстановление:

const stored = localStorage.getItem("awesomplete-data");
const list = stored ? stored.split("|") : [];

Такой формат уменьшает накладные расходы JSON, но требует строгого контроля символов-разделителей.


Приоритизация подсказок на основе частоты использования

localStorage можно использовать не только как хранилище истории, но и как простую систему веса.

function saveToStorage(value) {
    const key = "awesomplete-data";
    const stored = localStorage.getItem(key);

    let map = stored ? JSON.parse(stored) : {};

    map[value] = (map[value] || 0) + 1;

    localStorage.setItem(key, JSON.stringify(map));
}

Формирование списка для Awesomplete:

function buildList(map) {
    return Object.entries(map)
        .sort((a, b) => b[1] - a[1])
        .map(entry => entry[0]);
}

Это позволяет реализовать адаптивное автодополнение, где чаще используемые элементы поднимаются выше.


Очистка устаревших записей

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

function prune(map, threshold) {
    const result = {};

    for (const key in map) {
        if (map[key] >= threshold) {
            result[key] = map[key];
        }
    }

    return result;
}

Такая стратегия предотвращает деградацию качества подсказок и контролирует рост объёма данных в localStorage.