Пагинация в контексте Slim Select реализуется не как встроенный механизм библиотеки, а как внешний слой управления данными, который отвечает за подгрузку, фильтрацию и расширение набора опций. Slim Select работает с уже предоставленным массивом данных, поэтому задача пагинации сводится к организации последовательной поставки этих данных из источника (чаще всего API) и их корректному добавлению в компонент.
Ключевая особенность подхода заключается в том, что Slim Select не хранит состояние страниц — это полностью зона ответственности разработчика. Вся логика делится на три части:
Типовая схема работы строится вокруг параметров page и
limit. Сервер возвращает строго ограниченный набор
элементов, а клиент запрашивает их по мере необходимости.
Пример API-ответа:
{
"data": [
{ "id": 1, "text": "Option 1" },
{ "id": 2, "text": "Option 2" }
],
"hasMore": true
}
На стороне клиента хранится состояние:
let currentPage = 1;
let limit = 20;
let isLoading = false;
let hasMore = true;
Slim Select инициализируется пустым списком или начальным набором:
const select = new SlimSelect({
select: '#mySelect',
placeholder: 'Выбор элемента',
data: []
});
При старте страницы выполняется первый запрос:
async function loadPage(page) {
if (isLoading || !hasMore) return;
isLoading = true;
const response = await fetch(`/api/options?page=${page}&limit=${limit}`);
const result = await response.json();
const formatted = result.data.map(item => ({
text: item.text,
value: item.id
}));
select.setData([...select.data.data, ...formatted]);
hasMore = result.hasMore;
currentPage++;
isLoading = false;
}
Важно учитывать, что setData перезаписывает список,
поэтому необходимо сохранять уже загруженные элементы и объединять их с
новыми.
Slim Select предоставляет доступ к событиям открытия dropdown через DOM-обёртку. Это используется как триггер для подгрузки следующей страницы.
document.querySelector('#mySelect').addEventListener('click', () => {
loadPage(currentPage);
});
Однако такой подход недостаточен для реальной пагинации, так как пользователь может прокручивать список внутри dropdown.
Основной механизм постраничной загрузки строится вокруг события scroll внутри контейнера списка.
Slim Select создаёт собственный DOM-элемент, который можно отследить:
const dropdown = document.querySelector('.ss-content');
dropdown.addEventListener('scroll', () => {
const scrollPosition = dropdown.scrollTop + dropdown.clientHeight;
const threshold = dropdown.scrollHeight - 50;
if (scrollPosition >= threshold) {
loadPage(currentPage);
}
});
Здесь используется порог (threshold), который позволяет
заранее инициировать загрузку следующей страницы до достижения конца
списка.
При работе с асинхронной загрузкой критически важно предотвратить повторные вызовы API:
if (isLoading) return;
Дополнительно используется флаг hasMore, который
прекращает дальнейшие запросы:
if (!hasMore) return;
Эта комбинация защищает от ситуации, когда пользователь быстро прокручивает список и инициирует множество параллельных запросов.
Slim Select часто используется совместно с поисковой строкой, которая может влиять на пагинацию. В этом случае сервер должен возвращать данные уже отфильтрованные по запросу.
Состояние поиска:
let searchQuery = '';
Обновление при вводе:
document.querySelector('#searchInput').addEventListener('input', (e) => {
searchQuery = e.target.value;
resetAndLoad();
});
Сброс пагинации:
function resetAndLoad() {
currentPage = 1;
hasMore = true;
select.setData([]);
loadPage(currentPage);
}
Запрос с учётом поиска:
const response = await fetch(
`/api/options?page=${page}&limit=${limit}&q=${encodeURIComponent(searchQuery)}`
);
При больших объёмах данных (десятки тысяч записей) классическая пагинация становится менее эффективной. В таких случаях используется lazy loading с минимальной задержкой.
Подход основан на том, что данные добавляются в Slim Select без полной перерисовки компонента:
select.addData(formatted);
Этот метод предпочтительнее, чем setData, так как он не
сбрасывает текущий DOM.
Для повышения устойчивости интерфейса часто добавляется визуальный индикатор загрузки в список опций.
Пример добавления служебного элемента:
function showLoading() {
select.addData([
{
text: 'Загрузка...',
value: '__loading__',
disabled: true
}
]);
}
Удаление после загрузки:
function removeLoading() {
const filtered = select.data.data.filter(
item => item.value !== '__loading__'
);
select.setData(filtered);
}
При сетевых сбоях важно сохранить текущее состояние пагинации и не увеличивать счётчик страниц.
try {
await loadPage(currentPage);
} catch (e) {
isLoading = false;
console.error('Ошибка загрузки данных', e);
}
Распространённая ошибка — увеличение currentPage до
успешного ответа. Корректная логика предполагает инкремент только после
успешного получения данных.
При частом скролле необходимо ограничить количество вызовов загрузки:
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
Применение:
dropdown.addEventListener('scroll', debounce(() => {
const scrollPosition = dropdown.scrollTop + dropdown.clientHeight;
const threshold = dropdown.scrollHeight - 50;
if (scrollPosition >= threshold) {
loadPage(currentPage);
}
}, 100));
При сложных интерфейсах состояние пагинации часто выносится наружу (например, в Vuex, Redux или собственный store).
Минимальная модель состояния:
const state = {
page: 1,
limit: 20,
query: '',
items: [],
hasMore: true,
loading: false
};
Обновление Slim Select становится отражением состояния:
function syncSelect() {
select.setData(state.items.map(i => ({
text: i.text,
value: i.id
})));
}
При частых обновлениях важно минимизировать вызовы
setData. Оптимизация достигается через батчинг:
let buffer = [];
function pushToBuffer(items) {
buffer.push(...items);
if (buffer.length > 0) {
requestAnimationFrame(flushBuffer);
}
}
function flushBuffer() {
state.items.push(...buffer);
buffer = [];
syncSelect();
}
Если API поддерживает сортировку, она должна быть частью запроса и состояния:
let sort = 'asc';
fetch(`/api/options?page=${page}&limit=${limit}&sort=${sort}`);
Изменение сортировки всегда требует сброса пагинации:
function changeSort(newSort) {
sort = newSort;
resetAndLoad();
}
При длительной работе интерфейса список может становиться слишком большим. В таких случаях применяется ограничение кеша:
const MAX_ITEMS = 200;
function trimItems() {
if (state.items.length > MAX_ITEMS) {
state.items = state.items.slice(-MAX_ITEMS);
}
}
После обрезки необходимо синхронизировать Slim Select:
syncSelect();
Slim Select может пересоздавать DOM-элементы dropdown при каждом открытии. Поэтому привязка событий scroll должна выполняться повторно:
select.on('open', () => {
const dropdown = document.querySelector('.ss-content');
dropdown.onscr oll = () => {
const scrollPosition = dropdown.scrollTop + dropdown.clientHeight;
const threshold = dropdown.scrollHeight - 50;
if (scrollPosition >= threshold) {
loadPage(currentPage);
}
};
});
Если часть фильтрации выполняется на клиенте, необходимо разделять два режима:
Логика переключения:
function shouldUseServerFilter(query) {
return query.length > 2;
}
При коротких запросах используется локальная фильтрация:
const filtered = state.items.filter(item =>
item.text.toLowerCase().includes(query.toLowerCase())
);
При длинных запросах — серверная пагинация с reset состояния.
При росте сложности интерфейса пагинация превращается в потоковую модель:
Такой подход позволяет стабильно работать даже с большими справочниками и динамическими API без деградации UX.