Singleton для глобальных туров

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

Создание Singleton объекта

Singleton в контексте Shepherd.js представляет собой объект тура, который создается один раз и переиспользуется в любом месте приложения. Основная идея заключается в том, чтобы проверять существование экземпляра перед его созданием.

Пример базового Singleton для тура:

import Shepherd from 'shepherd.js';
import 'shepherd.js/dist/css/shepherd.css';

class TourSingleton {
  constructor() {
    if (!TourSingleton.instance) {
      this.tour = new Shepherd.Tour({
        defaultStepOptions: {
          cancelIcon: {
            enabled: true
          },
          scrollTo: { beh * avior: 'smooth', block: 'center' }
        },
        useModalOverlay: true
      });
      TourSingleton.instance = this;
    }
    return TourSingleton.instance;
  }

  getTour() {
    return this.tour;
  }

  addStep(stepOptions) {
    this.tour.addStep(stepOptions);
  }

  startTour() {
    this.tour.start();
  }
}

const tourInstance = new TourSingleton();
Object.freeze(tourInstance);

export default tourInstance;

Ключевые моменты:

  • Проверка существования экземпляра: if (!TourSingleton.instance) гарантирует, что объект тура создается один раз.
  • Методы для управления туром: addStep, startTour и getTour позволяют централизованно управлять шагами и запуском тура.
  • Заморозка объекта: Object.freeze(tourInstance) предотвращает изменение экземпляра после создания, что обеспечивает сохранение единственного состояния.

Добавление шагов тура

После создания Singleton объекта можно динамически добавлять шаги в тур. Например:

import tourInstance from './TourSingleton';

tourInstance.addStep({
  id: 'welcome',
  text: 'Добро пожаловать на сайт!',
  attachTo: {
    element: '.header-logo',
    on: 'bottom'
  },
  buttons: [
    {
      text: 'Далее',
      action: tourInstance.getTour().next
    }
  ]
});

tourInstance.addStep({
  id: 'menu',
  text: 'Здесь находятся основные разделы.',
  attachTo: {
    element: '.main-menu',
    on: 'right'
  },
  buttons: [
    {
      text: 'Назад',
      action: tourInstance.getTour().back
    },
    {
      text: 'Далее',
      action: tourInstance.getTour().next
    }
  ]
});

Особенности добавления шагов:

  • Каждый шаг должен иметь уникальный id для корректного управления навигацией.
  • attachTo задает элемент, к которому привязан шаг, и положение подсказки относительно него.
  • Кнопки шагов используют методы next, back, complete для управления навигацией по туру.

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

Для сложных приложений важно хранить текущее состояние тура: завершен ли он, на каком шаге находится пользователь и т.д. Это позволяет, например, показывать тур только при первом посещении страницы. Реализация может выглядеть так:

class TourSingleton {
  constructor() {
    if (!TourSingleton.instance) {
      this.tour = new Shepherd.Tour({
        defaultStepOptions: { scrollTo: true },
        useModalOverlay: true
      });
      this.isCompleted = false;
      TourSingleton.instance = this;
    }
    return TourSingleton.instance;
  }

  markCompleted() {
    this.isCompleted = true;
  }

  hasCompleted() {
    return this.isCompleted;
  }
}

const tourInstance = new TourSingleton();
Object.freeze(tourInstance);

export default tourInstance;
  • isCompleted хранит состояние завершения тура.
  • Методы markCompleted и hasCompleted позволяют проверять состояние перед запуском и предотвращать повторное отображение.

Использование Singleton в разных модулях

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

import tourInstance from './TourSingleton';

if (!tourInstance.hasCompleted()) {
  tourInstance.startTour();
}

Это обеспечивает:

  • Единое управление всеми шагами тура.
  • Возможность централизованного изменения текста, порядка шагов и логики навигации.
  • Минимизацию конфликтов при использовании нескольких компонентов, где требуется доступ к одному и тому же туру.

Интеграция с динамическим контентом

Если элементы страницы создаются динамически, необходимо убедиться, что шаги тура добавляются только после рендеринга этих элементов. Для этого удобно использовать методы типа MutationObserver или события фреймворков (React, Vue, Angular).

const observer = new MutationObserver(() => {
  const menuElement = document.querySelector('.main-menu');
  if (menuElement) {
    tourInstance.addStep({
      id: 'dynamic-step',
      text: 'Это динамический элемент меню.',
      attachTo: { element: menuElement, on: 'right' },
      buttons: [{ text: 'Закрыть', action: tourInstance.getTour().complete }]
    });
    observer.disconnect();
  }
});

observer.observe(document.body, { childList: true, subtree: true });

Такой подход гарантирует, что Singleton тур будет корректно работать даже с динамически загружаемым контентом.

Рекомендации по организации Singleton

  • Хранить все шаги и логику управления туром в одном модуле.
  • Разделять контент и конфигурацию шагов от бизнес-логики приложения.
  • Использовать методы состояния для контроля повторного показа.
  • При необходимости расширять Singleton дополнительными методами для событий (onShow, onComplete, onCancel) Shepherd.js.

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