SortableJS предоставляет гибкий механизм для организации перетаскивания элементов в списках, включая поддержку вложенных структур, таких как древовидные меню или списки с подкатегориями. Важным аспектом работы с вложенными списками является управление контейнерами и родительскими элементами, чтобы перемещение элементов происходило корректно и предсказуемо.
Вложенный список обычно строится с использованием стандартной
HTML-разметки <ul> и <li>:
<ul id="parent-list">
<li>Элемент 1</li>
<li>Элемент 2
<ul>
<li>Подэлемент 2.1</li>
<li>Подэлемент 2.2</li>
</ul>
</li>
<li>Элемент 3</li>
</ul>
Каждый <ul> может быть инициализирован как
отдельный контейнер SortableJS, что позволяет создавать независимые зоны
перетаскивания для подэлементов.
Для реализации вложенного списка необходимо создать основной
Sortable для верхнего уровня и дополнительные экземпляры для
каждого вложенного <ul>:
const parentList = document.getElementById('parent-list');
Sortable.create(parentList, {
group: 'nested',
animation: 150,
fallbackOnBody: true,
swapThreshold: 0.65
});
parentList.querySelectorAll('ul').forEach(subList => {
Sortable.create(subList, {
group: 'nested',
animation: 150,
fallbackOnBody: true,
swapThreshold: 0.65
});
});
Ключевые параметры:
group — позволяет объединять списки в одну группу для
поддержки перемещения элементов между уровнями.animation — задаёт плавность анимации при
перемещении.fallbackOnBody — предотвращает проблемы с
позиционированием в сложных контейнерах.swapThreshold — управляет чувствительностью
перетаскивания при смене позиции элемента.Часто требуется создавать или удалять вложенные списки
динамически. В таких случаях важно повторно
инициализировать SortableJS на вновь добавленных
<ul>:
function addSubListItem(parentLi, text) {
let subList = parentLi.querySelector('ul');
if (!subList) {
subList = document.createElement('ul');
parentLi.appendChild(subList);
Sortable.create(subList, {
group: 'nested',
animation: 150
});
}
const newItem = document.createElement('li');
newItem.textContent = text;
subList.appendChild(newItem);
}
Это гарантирует, что каждый новый подсписок получает те же возможности перетаскивания, что и статично определённые списки.
Иногда необходимо ограничивать глубину вложенности для предотвращения
слишком сложной структуры. Это можно реализовать с помощью события
onMove, проверяя уровень вложенности целевого элемента:
Sortable.create(parentList, {
group: 'nested',
animation: 150,
onMove: function (evt) {
let depth = 0;
let current = evt.to;
while (current && current !== parentList) {
if (current.tagName === 'UL') depth++;
current = current.parentNode;
}
return depth <= 2; // ограничение глубины вложенности до 2 уровней
}
});
SortableJS предоставляет множество событий, полезных для вложенных списков:
onEnd — позволяет обновить порядок элементов после
завершения перемещения.onAdd и onRemove — помогают
синхронизировать данные при переносе элементов между списками.onSort — срабатывает при каждом изменении порядка
внутри контейнера.Для вложенных списков важно правильно определять родителя элемента при обработке событий, чтобы корректно сохранять структуру:
parentList.addEventListener('sortupdate', function(evt) {
const movedItem = evt.item;
const newParent = movedItem.parentNode.closest('li');
const parentId = newParent ? newParent.dataset.id : null;
console.log('Элемент перемещён. Новый родитель ID:', parentId);
});
С помощью свойства group можно организовать
перемещение элементов между уровнями вложенности.
Значение group может быть объектом:
Sortable.create(parentList, {
group: {
name: 'nested',
pull: true,
put: true
},
animation: 150
});
pull: true — элемент можно перемещать из этого
списка.put: true — элемент можно помещать в этот список.Такой подход позволяет создавать гибкие интерфейсы с перемещением элементов из верхнего уровня в подсписки и наоборот.
Для улучшения восприятия можно использовать класс
ghostClass, который применяется к перетаскиваемому
элементу:
Sortable.create(parentList, {
group: 'nested',
animation: 150,
ghostClass: 'sortable-ghost'
});
Это позволяет визуально отделять перемещаемый элемент от остальных, особенно в сложных вложенных структурах.
Для сохранения состояния вложенных списков удобно использовать рекурсивное преобразование DOM в JSON:
function serializeList(ul) {
return Array.from(ul.children).map(li => {
const childUl = li.querySelector('ul');
return {
text: li.firstChild.textContent.trim(),
children: childUl ? serializeList(childUl) : []
};
});
}
const data = serializeList(parentList);
console.log(JSON.stringify(data, null, 2));
Такой подход позволяет хранить структуру для последующей обработки на сервере или в приложении.
Вложенные списки в SortableJS дают возможность создавать сложные иерархические интерфейсы с контролируемым уровнем вложенности, поддержкой динамических изменений и сохранением структуры данных. Использование событий и параметров контейнера позволяет гибко настраивать логику перемещения элементов и обеспечивать удобный пользовательский опыт.