Создание wrapper классов

Wrapper классы в контексте библиотеки Shopify Draggable представляют собой обёртки вокруг базового API, позволяющие создавать более удобный, читаемый и расширяемый код для реализации drag-and-drop функционала. Такой подход особенно полезен при необходимости интеграции Draggable в сложные интерфейсы с повторяющимися компонентами и кастомной логикой.

Инкапсуляция Draggable

Инкапсуляция позволяет скрыть детали реализации и предоставить унифицированный интерфейс для работы с перетаскиваемыми элементами. В wrapper классе создаются методы для инициализации, подписки на события и управления состоянием элементов.

class DraggableWrapper {
  constructor(containerSelector, options = {}) {
    this.container = document.querySelector(containerSelector);
    this.options = options;
    this.draggable = null;
  }

  init() {
    if (!this.container) return;
    this.draggable = new Draggable(this.container, {
      draggable: this.options.draggable || '.draggable-item',
      mirror: this.options.mirror || { constrainDimensions: true },
      plugins: this.options.plugins || [],
    });
    this._bindEvents();
  }

  _bindEvents() {
    if (!this.draggable) return;
    this.draggable.on('drag:start', (evt) => this.onDragStart(evt));
    this.draggable.on('drag:move', (evt) => this.onDragMove(evt));
    this.draggable.on('drag:stop', (evt) => this.onDragStop(evt));
  }

  onDragStart(evt) {
    console.log('Drag started', evt.source);
  }

  onDragMove(evt) {
    console.log('Dragging', evt.sensorEvent.clientX, evt.sensorEvent.clientY);
  }

  onDragStop(evt) {
    console.log('Drag stopped', evt.source);
  }
}

Ключевое здесь — выделение приватных методов (_bindEvents) и публичных методов (init, onDragStart), что делает wrapper гибким и безопасным для расширения.

Расширяемость и кастомизация

Wrapper класс можно расширять, добавляя новые методы для динамического добавления и удаления draggable элементов или изменения конфигурации на лету:

addItem(selector) {
  const newItem = document.querySelector(selector);
  if (newItem && this.draggable) {
    this.draggable.add(newItem);
  }
}

removeItem(selector) {
  const item = document.querySelector(selector);
  if (item && this.draggable) {
    this.draggable.remove(item);
  }
}

updateOptions(newOptions) {
  this.options = { ...this.options, ...newOptions };
  this.draggable.destroy();
  this.init();
}

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

Работа с событиями через wrapper

Одним из преимуществ wrapper является централизованная обработка событий. Вместо прямой подписки на события Draggable можно реализовать систему колбеков или событийного эмиттера внутри класса:

on(eventName, callback) {
  if (!this.draggable) return;
  this.draggable.on(eventName, callback);
}

off(eventName, callback) {
  if (!this.draggable) return;
  this.draggable.off(eventName, callback);
}

Это создаёт чистый API для взаимодействия с внешними компонентами, позволяя подключать обработчики событий динамически.

Интеграция с другими библиотеками и плагинами

Wrapper класс упрощает подключение плагинов Draggable, таких как Plugins.SwapAnimation или Plugins.ResizeMirror. Плагин можно передавать через опции при инициализации, а wrapper обеспечивает корректное применение и настройку:

import { SwapAnimation } from '@shopify/draggable';

const wrapper = new DraggableWrapper('#list', {
  draggable: '.item',
  plugins: [SwapAnimation],
});
wrapper.init();

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

Поддержка нескольких контейнеров

Для интерфейсов с множеством списков или областей перетаскивания можно создать универсальный wrapper, поддерживающий несколько контейнеров:

class MultiContainerDraggable extends DraggableWrapper {
  constructor(containers, options = {}) {
    super(null, options);
    this.containers = containers.map(sel => document.querySelector(sel));
  }

  init() {
    this.draggable = new Draggable(this.containers, {
      draggable: this.options.draggable || '.draggable-item',
      plugins: this.options.plugins || [],
    });
    this._bindEvents();
  }
}

Это позволяет реализовать перемещение элементов между списками с минимальной дополнительной логикой.

Принципы хорошего wrapper класса

  • Инкапсуляция логики — скрывать внутренние методы и API Draggable.
  • Чистый публичный интерфейс — методы init, destroy, addItem, removeItem, on обеспечивают удобство использования.
  • Гибкость конфигурации — возможность обновления опций на лету.
  • Централизация обработки событий — упрощает интеграцию с другими частями приложения.
  • Расширяемость — легко добавлять новые функции или поддерживать несколько контейнеров.

Wrapper классы становятся не только удобным инструментом для управления Draggable, но и основой для построения сложных drag-and-drop интерфейсов с высокой повторяемостью и поддержкой кастомной логики.