VGTabs

Переключение панелей, клавиатура, индикатор и удалённый контент.

Hash и AJAX

Начальная вкладка из URL, удалённый HTML, пустой ответ, ошибка и JSON.

Каждый пример независим. Кнопка «Показать код» раскрывает HTML, JavaScript и CSS, которые используются в самом примере. Стили оболочки и кнопок предоставляет OKAUX 1.0.2.

Начальная вкладка из URL

data-hash читает hash при создании экземпляра; это не двусторонний роутер и не обработчик hashchange.

Открыть страницу сразу на вкладке «Документы». Ссылка перезагружает страницу; обычное переключение вкладок URL не меняет.

Сводка: содержимое самостоятельной панели.
Документы: содержимое самостоятельной панели.
HTML
<p><a href="?open=hash#tabs-hash-1">Открыть страницу сразу на вкладке «Документы»</a>. Ссылка перезагружает страницу; обычное переключение вкладок URL не меняет.</p>
<div class="tabs-demo" id="tabs-hash-demo">
  <div class="vg-tabs" data-hash="true">
    <div class="vg-tabs-panel" role="tablist" aria-label="Разделы примера">
      <button type="button" class="vg-tabs-link active" id="tabs-hash-0-tab" data-vg-toggle="tab" data-vg-target="#tabs-hash-0" aria-controls="tabs-hash-0">Сводка</button>
      <button type="button" class="vg-tabs-link" id="tabs-hash-1-tab" data-vg-toggle="tab" data-vg-target="#tabs-hash-1" aria-controls="tabs-hash-1">Документы</button>
    </div>
  </div>
  <div class="vg-tabs-content tabs-demo-content">
    <div class="vg-tabs-pane show active" id="tabs-hash-0" tabindex="0">Сводка: содержимое самостоятельной панели.</div>
    <div class="vg-tabs-pane" id="tabs-hash-1" tabindex="0">Документы: содержимое самостоятельной панели.</div>
  </div>
</div>
JavaScript
/**
 * Описание: декларативный пример VGTabs.
 * Возможности: автоматическая обработка вкладок через Data API.
 */
// Дополнительная инициализация не требуется: используется Data API.
CSS
/* Токены OKAUX в демо настроены с префиксом vg; состояние темы общее. */
.tabs-demo { min-width: 0; }
.tabs-demo-content {
  margin-top: 1rem;
  padding: 1rem;
  border: 1px solid var(--vg-border-default);
  border-radius: var(--vg-radius-md);
  background: var(--vg-bg-secondary);
  overflow-wrap: anywhere;
}
.tabs-demo-custom {
  --vg-tabs-panel-gap: 0.35rem;
  --vg-tabs-link-padding-x: 0.75rem;
  --vg-tabs-slider-border-radius: 999px;
  --vg-tabs-track-slider-border-radius: 999px;
}
.tabs-demo-vertical {
  display: grid;
  grid-template-columns: 150px minmax(0, 1fr);
  align-items: start;
  gap: 1rem;
}
.tabs-demo-vertical > .tabs-demo-content { margin-top: 0; }
@media (max-width: 575.98px) {
  .tabs-demo-vertical { grid-template-columns: minmax(0, 1fr); }
}
.tabs-demo-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; margin-bottom: 1rem; }
.tabs-demo-status { margin-top: 1rem; }
.tabs-demo-log { max-width: 100%; white-space: pre-wrap; overflow-wrap: anywhere; font-size: 0.85rem; }

Удалённый HTML и состояния

Реальный GET /api/tabs/demo. Задержка 400 мс задана через ajax.timeout для проверки загрузчика; ошибка намеренно возвращает HTTP 503.

Выберите удалённую вкладку. Для повторного открытия сначала перейдите в другую.
Контент ещё не загружен.
Контент ещё не загружен.
Ожидание ответа.
Ожидание ответа.

Запросов завершено: 0

HTML
<div class="tabs-demo" id="tabs-ajax-demo">
  <div class="vg-tabs">
    <div class="vg-tabs-panel" role="tablist" aria-label="Разделы примера">
      <button type="button" class="vg-tabs-link active" id="tabs-ajax-0-tab" data-vg-toggle="tab" data-vg-target="#tabs-ajax-0" aria-controls="tabs-ajax-0">Локально</button>
      <button type="button" class="vg-tabs-link" id="tabs-ajax-1-tab" data-vg-toggle="tab" data-vg-target="#tabs-ajax-1" aria-controls="tabs-ajax-1" data-ajax-route="/api/tabs/demo?mode=html" data-ajax-target="#tabs-ajax-1" data-ajax-loader="true" data-ajax-timeout="400">Один раз</button>
      <button type="button" class="vg-tabs-link" id="tabs-ajax-2-tab" data-vg-toggle="tab" data-vg-target="#tabs-ajax-2" aria-controls="tabs-ajax-2" data-ajax-route="/api/tabs/demo?mode=html" data-ajax-target="#tabs-ajax-2" data-ajax-once="false" data-ajax-loader="true" data-ajax-timeout="400">Каждый раз</button>
      <button type="button" class="vg-tabs-link" id="tabs-ajax-3-tab" data-vg-toggle="tab" data-vg-target="#tabs-ajax-3" aria-controls="tabs-ajax-3" data-ajax-route="/api/tabs/demo?mode=empty" data-ajax-target="#tabs-ajax-3" data-ajax-loader="true">Пусто</button>
      <button type="button" class="vg-tabs-link" id="tabs-ajax-4-tab" data-vg-toggle="tab" data-vg-target="#tabs-ajax-4" aria-controls="tabs-ajax-4" data-ajax-route="/api/tabs/demo?mode=error" data-ajax-target="#tabs-ajax-4" data-ajax-loader="true">Ошибка</button>
    </div>
  </div>
  <div class="vg-tabs-content tabs-demo-content">
    <div class="vg-tabs-pane show active" id="tabs-ajax-0" tabindex="0">Выберите удалённую вкладку. Для повторного открытия сначала перейдите в другую.</div>
    <div class="vg-tabs-pane" id="tabs-ajax-1" tabindex="0">Контент ещё не загружен.</div>
    <div class="vg-tabs-pane" id="tabs-ajax-2" tabindex="0">Контент ещё не загружен.</div>
    <div class="vg-tabs-pane" id="tabs-ajax-3" tabindex="0">Ожидание ответа.</div>
    <div class="vg-tabs-pane" id="tabs-ajax-4" tabindex="0">Ожидание ответа.</div>
  </div>
</div>
<p class="tabs-demo-status" id="tabs-ajax-status" role="status">Запросов завершено: 0</p>
JavaScript
/**
 * Описание: AJAX-состояния VGTabs.
 * Возможности: счётчик ответов, пустое состояние и повтор после ошибки.
 */
function initTabsDemo() {
  const root = document.querySelector('#tabs-ajax-demo');
  if (!root || root.dataset.ready) return;
  root.dataset.ready = 'true';
  let completed = 0;
  root.addEventListener('vg.tabs.loaded', event => {
    const panel = document.querySelector(event.target.getAttribute('data-vg-target'));
    const response = event.data?.response;
    if (event.stats === 'error') {
      panel.textContent = 'Тестовая ошибка HTTP 503. Переключитесь в другую вкладку и вернитесь, чтобы повторить запрос.';
    } else if (response === '') {
      panel.textContent = 'Записей пока нет. Сервер вернул пустую строку.';
    }
    document.querySelector('#tabs-ajax-status').textContent =
      'Запросов завершено: ' + (++completed) + '. Последний результат: ' + event.stats;
  });
}

initTabsDemo();
CSS
/* Токены OKAUX в демо настроены с префиксом vg; состояние темы общее. */
.tabs-demo { min-width: 0; }
.tabs-demo-content {
  margin-top: 1rem;
  padding: 1rem;
  border: 1px solid var(--vg-border-default);
  border-radius: var(--vg-radius-md);
  background: var(--vg-bg-secondary);
  overflow-wrap: anywhere;
}
.tabs-demo-custom {
  --vg-tabs-panel-gap: 0.35rem;
  --vg-tabs-link-padding-x: 0.75rem;
  --vg-tabs-slider-border-radius: 999px;
  --vg-tabs-track-slider-border-radius: 999px;
}
.tabs-demo-vertical {
  display: grid;
  grid-template-columns: 150px minmax(0, 1fr);
  align-items: start;
  gap: 1rem;
}
.tabs-demo-vertical > .tabs-demo-content { margin-top: 0; }
@media (max-width: 575.98px) {
  .tabs-demo-vertical { grid-template-columns: minmax(0, 1fr); }
}
.tabs-demo-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; margin-bottom: 1rem; }
.tabs-demo-status { margin-top: 1rem; }
.tabs-demo-log { max-width: 100%; white-space: pre-wrap; overflow-wrap: anywhere; font-size: 0.85rem; }

JSON без автоматического вывода

ajax.output=false оставляет рендер приложению. Поле event.stats содержит success или error, event.data.response — тело ответа.

Ответ JSON обрабатывается приложением без автоматической вставки HTML.
Данные ещё не загружены.
HTML
<div class="tabs-demo" id="tabs-output-demo">
  <div class="vg-tabs">
    <div class="vg-tabs-panel" role="tablist" aria-label="Разделы примера">
      <button type="button" class="vg-tabs-link active" id="tabs-output-0-tab" data-vg-toggle="tab" data-vg-target="#tabs-output-0" aria-controls="tabs-output-0">Описание</button>
      <button type="button" class="vg-tabs-link" id="tabs-output-1-tab" data-vg-toggle="tab" data-vg-target="#tabs-output-1" aria-controls="tabs-output-1" data-ajax-route="/api/tabs/demo?mode=json" data-ajax-output="false">Данные</button>
    </div>
  </div>
  <div class="vg-tabs-content tabs-demo-content">
    <div class="vg-tabs-pane show active" id="tabs-output-0" tabindex="0">Ответ JSON обрабатывается приложением без автоматической вставки HTML.</div>
    <div class="vg-tabs-pane" id="tabs-output-1" tabindex="0">Данные ещё не загружены.</div>
  </div>
</div>
JavaScript
/**
 * Описание: ручной вывод ответа VGTabs.
 * Возможности: обработка JSON при ajax.output=false.
 */
function initTabsDemo() {
  const root = document.querySelector('#tabs-output-demo');
  if (!root || root.dataset.ready) return;
  root.dataset.ready = 'true';
  root.addEventListener('vg.tabs.loaded', event => {
    document.querySelector('#tabs-output-1').textContent = event.stats === 'success'
      ? 'Открытых задач: ' + event.data.response.tasks + '. Источник: ' + event.data.response.source
      : 'Не удалось загрузить данные.';
  });
}

initTabsDemo();
CSS
/* Токены OKAUX в демо настроены с префиксом vg; состояние темы общее. */
.tabs-demo { min-width: 0; }
.tabs-demo-content {
  margin-top: 1rem;
  padding: 1rem;
  border: 1px solid var(--vg-border-default);
  border-radius: var(--vg-radius-md);
  background: var(--vg-bg-secondary);
  overflow-wrap: anywhere;
}
.tabs-demo-custom {
  --vg-tabs-panel-gap: 0.35rem;
  --vg-tabs-link-padding-x: 0.75rem;
  --vg-tabs-slider-border-radius: 999px;
  --vg-tabs-track-slider-border-radius: 999px;
}
.tabs-demo-vertical {
  display: grid;
  grid-template-columns: 150px minmax(0, 1fr);
  align-items: start;
  gap: 1rem;
}
.tabs-demo-vertical > .tabs-demo-content { margin-top: 0; }
@media (max-width: 575.98px) {
  .tabs-demo-vertical { grid-template-columns: minmax(0, 1fr); }
}
.tabs-demo-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; margin-bottom: 1rem; }
.tabs-demo-status { margin-top: 1rem; }
.tabs-demo-log { max-width: 100%; white-space: pre-wrap; overflow-wrap: anywhere; font-size: 0.85rem; }