Реальный поиск, пагинация, пустой ответ, ошибка и сохранённый выбор.
VGSelect сохраняет исходный <select> для формы и строит интерфейс рядом с ним.
Каждый пример запускается один раз после появления HTML. Параметры из data-* читаются при инициализации;
библиотека сама не сканирует страницу в поисках полей. По умолчанию модуль использует
position: 'auto': список выбирает сторону с большим свободным местом.
Автомобили: мультивыбор и AJAX-поиск
20 автомобилей. Введите марку или модель латиницей: Toyota, BMW, Audi. По 5 результатов на страницу.
Автомобили: мультивыбор с AJAX-поиском
Введите Toyota, BMW или Audi.
Показать код
<div class="select-demo">
<label for="select-remote-cars">Автомобили: мультивыбор с AJAX-поиском</label>
<select id="select-remote-cars" class="vg-select" name="cars[]" multiple data-select-demo data-placeholder="Введите марку или модель"></select>
<output id="select-remote-cars-status" aria-live="polite">Введите Toyota, BMW или Audi.</output>
</div>
/**
* Описание: рабочий пример VGSelect — AJAX-мультивыбор автомобилей.
* Возможности: инициализация и демонстрация публичного контракта на исходном select.
*/
import {VGSelect} from 'vgapp';
function initSelectDemo() {
const select = document.querySelector('#select-remote-cars');
if (!select || select.dataset.inited === 'true') return;
const status = document.querySelector('#select-remote-cars-status');
VGSelect.init(select, {
close: false,
search: {remote: true, route: '/api/select/demo?dataset=cars', pagination: true, perpage: 5, minterm: 1, delay: 300},
onSearch(wrapper, payload) {
if (!payload || !('term' in payload)) return;
status.textContent = payload.data
? (payload.data.results.length ? 'Получено: ' + payload.data.results.length : 'Ничего не найдено')
: 'Ожидание поиска: ' + payload.term;
},
onLoadNext(wrapper, payload) {
status.textContent = 'Загружена страница ' + payload.page;
}
});
select.nextElementSibling.addEventListener('vg.select.error', event => {
status.textContent = 'Ошибка: ' + event.detail.error + '. Введите другой запрос.';
});
select.addEventListener('change', () => { status.textContent = 'Выбрано: ' + (Array.from(select.selectedOptions, option => option.text).join(', ') || 'ничего'); });
}
initSelectDemo();
.select-demo {
display: grid;
gap: 12px;
max-width: 560px;
min-width: 0;
}
.select-demo-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.select-demo output {
overflow-wrap: anywhere;
}
.select-demo-log {
max-height: 240px;
overflow: auto;
white-space: pre-wrap;
overflow-wrap: anywhere;
padding: 12px;
border: 1px solid var(--vg-border-default);
border-radius: var(--vg-radius-md);
background: var(--vg-surface-muted-bg);
color: var(--vg-text-primary);
}
.select-demo-placement {
padding-top: 180px;
}
.vg-select.select-demo-compact {
--vg-select-current-font-size: 14px;
--vg-select-current-padding-top: 8px;
--vg-select-current-padding-bottom: 8px;
--vg-select-current-border-radius: 10px;
--vg-select-list-hover-background-color: var(--vg-surface-muted-bg);
}
.module-select-content {
min-width: 0;
}
.module-select-reference {
margin-top: 32px;
overflow-wrap: anywhere;
}
.module-select-reference dt {
margin-top: 16px;
font-weight: 600;
}
.module-select-reference dd {
margin: 6px 0 0;
}
Примеры городов используют тестовый GET endpoint /api/select/demo с параметрами
q, page и per_page. Это фиксированный справочник городов, а не production API.
Введите м для нескольких страниц, empty для пустого ответа или error для HTTP 503.
После ошибки введите ка, чтобы проверить восстановление. Индикатор загрузки виден на время запроса;
для его наблюдения можно включить ограничение скорости сети.
Три результата на страницу, задержка 300 мс. Журнал под полем показывает начало поиска, результат, пагинацию и ошибку. Сообщения журнала — UI демо, а не встроенный alert VGSelect.
Удалённый поиск города
Откройте список и введите запрос.
Показать код
<div class="select-demo">
<label for="select-remote">Удалённый поиск города</label>
<select id="select-remote" class="vg-select" name="remote_city" data-select-demo
data-placeholder="Введите м, empty или error">
<option value="" selected hidden></option>
</select>
<output id="select-remote-status" aria-live="polite">Откройте список и введите запрос.</output>
</div>
/**
* Описание: рабочий пример VGSelect — remote.
* Возможности: инициализация и демонстрация публичного контракта на исходном select.
*/
import {VGSelect} from 'vgapp';
function initSelectDemo() {
const select = document.querySelector('#select-remote');
if (!select || select.dataset.inited === 'true') return;
const status = document.querySelector('#select-remote-status');
VGSelect.init(select, {
search: {remote: true, route: '/api/select/demo', pagination: true, perpage: 3, minterm: 1, delay: 300},
onSearch(wrapper, payload) {
if (!payload || !('term' in payload)) return;
status.textContent = payload.data
? (payload.data.results.length ? 'Получено: ' + payload.data.results.length : 'Ничего не найдено')
: 'Ожидание поиска: ' + payload.term;
},
onLoadNext(wrapper, payload) {
status.textContent = 'Загружена страница ' + payload.page;
}
});
select.nextElementSibling.addEventListener('vg.select.error', event => {
status.textContent = 'Ошибка: ' + event.detail.error + '. Введите другой запрос.';
});
select.addEventListener('change', () => { status.textContent = 'Выбрано: ' + select.value; });
}
initSelectDemo();
.select-demo {
display: grid;
gap: 12px;
max-width: 560px;
min-width: 0;
}
.select-demo-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.select-demo output {
overflow-wrap: anywhere;
}
.select-demo-log {
max-height: 240px;
overflow: auto;
white-space: pre-wrap;
overflow-wrap: anywhere;
padding: 12px;
border: 1px solid var(--vg-border-default);
border-radius: var(--vg-radius-md);
background: var(--vg-surface-muted-bg);
color: var(--vg-text-primary);
}
.select-demo-placement {
padding-top: 180px;
}
.vg-select.select-demo-compact {
--vg-select-current-font-size: 14px;
--vg-select-current-padding-top: 8px;
--vg-select-current-padding-bottom: 8px;
--vg-select-current-border-radius: 10px;
--vg-select-list-hover-background-color: var(--vg-surface-muted-bg);
}
.module-select-content {
min-width: 0;
}
.module-select-reference {
margin-top: 32px;
overflow-wrap: anywhere;
}
.module-select-reference dt {
margin-top: 16px;
font-weight: 600;
}
.module-select-reference dd {
margin: 6px 0 0;
}
Редактирование с сохранённым значением
Калуга загружена заранее и помечена data-preserve. Введите «м»: сохранённая опция останется в списке при замене результатов. Для других выбранных значений такой гарантии нет без data-preserve.
Город в редактируемой записи
Калуга
Сохранённое значение: Калуга (9).
Показать код
<div class="select-demo">
<label for="select-preloaded">Город в редактируемой записи</label>
<select id="select-preloaded" class="vg-select" name="saved_city" data-select-demo>
<option value="9" selected data-preserve>Калуга</option>
</select>
<output id="select-preloaded-status" aria-live="polite">Сохранённое значение: Калуга (9).</output>
</div>
/**
* Описание: рабочий пример VGSelect — preloaded.
* Возможности: инициализация и демонстрация публичного контракта на исходном select.
*/
import {VGSelect} from 'vgapp';
function initSelectDemo() {
const select = document.querySelector('#select-preloaded');
if (!select || select.dataset.inited === 'true') return;
const status = document.querySelector('#select-preloaded-status');
VGSelect.init(select, {
search: {remote: true, route: '/api/select/demo', pagination: true, perpage: 3},
onSearch(wrapper, payload) {
if (payload?.data) status.textContent = 'Результатов: ' + payload.data.results.length + '; текущее значение: ' + select.value;
}
});
select.nextElementSibling.addEventListener('vg.select.error', () => {
status.textContent = 'Ошибка запроса. Сохранённая опция остаётся доступной.';
});
select.addEventListener('change', () => { status.textContent = 'Выбрано: ' + select.value; });
// data-preserve сохраняет именно эту опцию, а не любой последующий выбор.
// addOptions не удаляет дубликаты id: production endpoint должен исключать уже сохранённые id.
}
initSelectDemo();
.select-demo {
display: grid;
gap: 12px;
max-width: 560px;
min-width: 0;
}
.select-demo-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.select-demo output {
overflow-wrap: anywhere;
}
.select-demo-log {
max-height: 240px;
overflow: auto;
white-space: pre-wrap;
overflow-wrap: anywhere;
padding: 12px;
border: 1px solid var(--vg-border-default);
border-radius: var(--vg-radius-md);
background: var(--vg-surface-muted-bg);
color: var(--vg-text-primary);
}
.select-demo-placement {
padding-top: 180px;
}
.vg-select.select-demo-compact {
--vg-select-current-font-size: 14px;
--vg-select-current-padding-top: 8px;
--vg-select-current-padding-bottom: 8px;
--vg-select-current-border-radius: 10px;
--vg-select-list-hover-background-color: var(--vg-surface-muted-bg);
}
.module-select-content {
min-width: 0;
}
.module-select-reference {
margin-top: 32px;
overflow-wrap: anywhere;
}
.module-select-reference dt {
margin-top: 16px;
font-weight: 600;
}
.module-select-reference dd {
margin: 6px 0 0;
}
Формат ответа
{
"results": [{ "id": "1", "text": "Москва" }],
"pagination": { "current_page": 1, "total_pages": 3 }
}
Используйте уникальные строковые id. Для пустого ответа: results: [] и
total_pages: 1. Названия параметров в JavaScript — minterm и perpage,
в нижнем регистре. Сервер получает per_page. Поиск запускается вводом, не открытием списка.