jQuery Sortable for nested lists
by Rolf Friedrich
HTML
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
<label>Cursor At:</label>
<label id="cursor"></label>
<h3>
Targetlist
</h3>
<ul id="targetlist" class='targetlist'>
<li class="unsortable">Unsortable Item </li>
<li>
<ol class="items" id="item_1">
<li class="unsortable">Item 1</li>
</ol>
</li>
<li>
<ol class="items" id="item_2">
<li class="unsortable">Item 2</li>
</ol>
</li>
</ul>
<h3>
Sourcelist
</h3>
<ul id="sourcelist" class="secondlist">
<li class="item sourceitem">Source item</li>
<li class="item sourceitem">Source item</li>
<li class="item sourceitem">Source item</li>
<li class="item sourceitem">Source item</li>
</ul>
CSS
ul,
.items,
.secondlist {
list-style: none outside none;
/* margin-bottom: 20px; */
padding: 0;
width: 100%;
position: relative;
}
ol {
list-style: none outside none;
margin: 0;
padding: 0;
}
ol.disabled li {
background-color: #ffb7b7;
}
li.disabled {
background:#f00 !important;
display:none !important ;
}
ol.disabled .placeholder {
display:none !important;
}
ol.enabled li{
background: #d8ffdf;
}
ul .item,
.unsortable{
background: #fff;
border: 1px solid #ddd;
cursor: move;
/* height: 50px; */
line-height: 50px;
padding: 0 10px;
width: 50%;
}
ul li:has(ol) {
padding:0;
}
li.unsortable,
.item.unsortable {
cursor: default !important;
background: #fff;
pointer-events: none;
}
li:hover,
.item:hover{
/*background: #d8ffdf;*/
cursor:move;
}
li:has(ol):hover,
.item:has(ol):hover{
background: #fff;
}
.item:hover ol:empty{
background: #ff0;
min-height:2rem;
}
.item.unsortable:hover{
cursor: not-allowed;
background: #ffb7b7;
}
ol.open{
min-height:4rem;
}
.nested {
margin-left: 20px;
width: 50%;
}
.dragHover {
border-color: blue;
color: blue;
}
.placeholder {
background: #fff;
border: 1px dashed #ccc;
height: 50px;
width: 50%;
}
.placeholder-nested {
background: #fff;
border: 1px dashed #ccc;
height: 50px;
width: 480px;
margin-left: 20px;
}
.secondlist{
min-height: 4rem;
}
JavaScript
var sortable = Sortable.create( document.getElementById('sourcelist'), {
filter: '.unsortable',
draggable: '.item',
ghostClass :'placeholder-nested',
group: {
name: 'items',
},
sort: false,
animation: 100,
onMove: function( evt, originalEvent) {
evt.willInsertAfter= true;
let condition = evt.to.children.length >= 2;
if (condition) {
if( evt.dragged.classList.contains('enabled') ){
evt.dragged.classList.remove('enabled');
}
evt.dragged.classList.add('disabled');
if( evt.related.classList.contains('items') ) {
evt.related.classList.add('disabled');
}
else{
evt.related.closest('.items').classList.add('disabled');
}
}
else{
if( evt.dragged.classList.contains('disabled') ){
evt.dragged.classList.remove('disabled');
}
evt.dragged.classList.add('enabled');
if( evt.related.classList.contains('items') ) {
evt.related.classList.add('enabled');
}
else{
evt.related.closest('.items').classList.add('enabled');
}
//remove all
document.querySelectorAll('.disabled').forEach( disabled => {
disabled.classList.remove('disabled');
});
document.querySelectorAll('ol.enabled').forEach( enabled => {
enabled.classList.remove('enabled');
});
}
},
onEnd: function ( evt) {
//remove all
document.querySelectorAll('.disabled').forEach( disabled => {
disabled.classList.remove('disabled');
});
document.querySelectorAll('.enabled').forEach( enabled => {
enabled.classList.remove('enabled');
});
let condition = evt.to.children.length <= 2;
if (!condition) {
//remove "disabled" class on cancel
if(...