Работа с вложенными структурами

Dragula — это легковесная библиотека JavaScript для реализации функционала drag-and-drop без сложных зависимостей. Её основной принцип работы заключается в манипуляции DOM-элементами при перемещении внутри одного контейнера или между несколькими контейнерами. Библиотека обеспечивает плавную анимацию, удобное управление событиями и минимальные настройки для интеграции.

В Dragula все взаимодействия строятся вокруг контейнеров и элементов, которые в них находятся. Контейнер — это HTML-элемент, внутри которого находятся элементы, доступные для перетаскивания. Элемент — это объект, который можно перемещать между контейнерами или внутри одного контейнера.

const container = document.querySelector('#container');
dragula([container]);

В этом примере создаётся базовая зона перетаскивания. Элементы внутри контейнера становятся интерактивными без дополнительной конфигурации.


Работа с вложенными структурами

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

const parentContainer = document.querySelector('#parent');
const childContainers = parentContainer.querySelectorAll('.child');

dragula([parentContainer, ...childContainers], {
    moves: function (el, source, handle, sibling) {
        return !el.classList.contains('locked'); // предотвращение перемещения заблокированных элементов
    }
});

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

  • Вложенные контейнеры могут быть одновременно источниками и приёмниками.
  • moves позволяет ограничивать возможность перетаскивания элементов в зависимости от класса или состояния.
  • Распространённая практика — использовать атрибуты data-* для идентификации уровня вложенности и контроля допустимых перемещений.

Настройка правил перемещения

Dragula предоставляет несколько функций для контроля логики drag-and-drop:

  • accepts — определяет, может ли элемент быть помещён в конкретный контейнер.
  • invalid — запрещает перемещение определённых элементов.
  • copy — позволяет создавать копии элементов вместо их перемещения.

Пример настройки для вложенных списков:

dragula([parentContainer, ...childContainers], {
    moves: (el) => !el.classList.contains('locked'),
    accepts: (el, target, source, sibling) => {
        const sourceLevel = source.dataset.level;
        const targetLevel = target.dataset.level;
        return parseInt(targetLevel) <= parseInt(sourceLevel) + 1;
    },
    copy: false
});

Здесь используется проверка dataset.level, чтобы элементы не могли быть перемещены на уровень выше, чем разрешено. Такой подход позволяет строить строгую иерархию.


Обработка событий

Dragula генерирует несколько событий, которые критически важны для работы с вложенными структурами:

  • drag — событие начала перетаскивания.
  • dragend — завершение перетаскивания.
  • drop — элемент успешно перемещён в контейнер.
  • over / out — элемент находится над контейнером или покинул его.

Пример использования событий для обновления данных и контроля вложенности:

drake.on('drop', (el, target, source, sibling) => {
    const parentId = target.dataset.id;
    const elementId = el.dataset.id;
    console.log(`Элемент ${elementId} перемещён в контейнер ${parentId}`);
    updateHierarchy(el, target);
});

Функция updateHierarchy может изменять внутреннюю структуру данных, синхронизируя DOM с моделью приложения.


Управление визуальными эффектами

Для удобства работы с вложенными структурами часто применяются стили для hover, dragging и placeholder:

.gu-mirror {
    opacity: 0.8;
    border: 2px dashed #ccc;
}

.gu-transit {
    opacity: 0.5;
}

.container.over {
    background-color: #f0f0f0;
}
  • .gu-mirror — отображение элемента во время перетаскивания.
  • .gu-transit — элемент, который временно покидает исходный контейнер.
  • .over — визуальная подсветка контейнера, когда элемент над ним.

Оптимизация работы с большими деревьями

При глубокой вложенности и большом количестве элементов рекомендуется:

  1. Инициализировать Dragula на массиве контейнеров вместо отдельных вызовов.
  2. Использовать делегирование событий и минимизировать прямые манипуляции DOM.
  3. Сохранять состояние вложенности через атрибуты data-* для упрощённого контроля правил перемещения.
  4. Предварительно фильтровать запрещённые элементы через moves и accepts, чтобы снизить нагрузку на обработку событий.

Интеграция с динамически создаваемыми элементами

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

function refreshContainers() {
    const allContainers = document.querySelectorAll('.container');
    drake.containers = Array.from(allContainers);
}

document.querySelector('#add-item').addEventListener('click', () => {
    const newItem = document.createElement('div');
    newItem.classList.add('item');
    document.querySelector('#parent').appendChild(newItem);
    refreshContainers();
});

Такой подход гарантирует, что новые элементы автоматически участвуют в drag-and-drop без полной переинициализации Dragula.


Работа с деревьями и иерархиями данных

Dragula не ограничивает структуру данных в приложении. Часто используется синхронизация DOM с JSON-моделью:

function serialize(container) {
    const children = Array.from(container.children);
    return children.map(child => ({
        id: child.dataset.id,
        children: serialize(child.querySelector('.children') || document.createElement('div'))
    }));
}

Это позволяет поддерживать вложенные списки в виде дерева и легко интегрировать с серверной частью или локальным хранилищем.


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