feat(templates/pages/kanban): add drag-and-drop sortable column functionality and improve card movement handling

- New Features: Introduced sortable column UI with drag-and-drop reordering, visual ghost and drag styling, and automatic card movement via API.
- Enhancements: Added CSS classes for ghost and drag effects, integrated htmx `afterSwap` event to reinitialize sortable after dynamic swaps, and improved delay and filter settings.
- Bug Fixes: Fixed column identification logic, ensured proper cleanup of existing sortable instances, and enhanced error handling to reload page on failed moves.
This commit is contained in:
sankalp committed 2026-05-28 00:31:34 +05:45
1 parent 049a152704
commit d04ee3cd6f
1 file changed
+42 -15
+42 -15
View File
@@ -81,6 +81,13 @@
<label class="modal-backdrop" for="delete-card-modal"></label>
</div>
{% endblock %}
<style>
.sortable-ghost { opacity: 0.3; }
.sortable-drag {
box-shadow: 0 20px 60px rgba(0,0,0,0.2) !important;
transform: rotate(3deg) !important;
}
</style>
{% block extra_scripts %}
<script>
function openCardDeleteModal(actionUrl) {
@@ -103,24 +110,39 @@
return cookieValue;
}
document.addEventListener('DOMContentLoaded', function() {
function initKanbanSortable() {
const containers = document.querySelectorAll('[data-sortable-column]');
const csrftoken = getCookie('csrftoken');
if (!csrftoken) return;
containers.forEach(container => {
new Sortable(container, {
group: 'shared',
containers.forEach(function(container) {
if (container.sortableInstance) return;
container.sortableInstance = new Sortable(container, {
group: {
name: 'kanban',
pull: true,
put: true
},
animation: 150,
ghostClass: 'opacity-50',
dragClass: '!shadow-xl !rotate-3',
delay: 100,
ghostClass: 'sortable-ghost',
dragClass: 'sortable-drag',
delay: 150,
delayOnTouchOnly: true,
filter: 'button, input, textarea, select, a',
preventOnFilter: true,
filter: '.no-drag, a, button, input, textarea, select',
preventOnFilter: false,
scroll: true,
bubbleScroll: true,
onEnd: function(evt) {
const cardId = evt.item.id.replace('card-', '');
const newColumnId = evt.to.id.replace('column-', '');
const order = evt.newIndex;
var item = evt.item;
var to = evt.to;
if (!item || !to) return;
var cardId = item.getAttribute('data-card-id');
var newColumnId = to.getAttribute('data-sortable-column');
var order = evt.newIndex;
if (!cardId || !newColumnId || order === undefined) return;
fetch('/cards/' + cardId + '/move/', {
method: 'POST',
@@ -129,12 +151,17 @@
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'column_id=' + newColumnId + '&order=' + order
}).catch(function(err) {
console.error('Drag drop failed:', err);
}).then(function(r) {
if (!r.ok) location.reload();
}).catch(function() {
location.reload();
});
}
});
});
});
}
document.addEventListener('DOMContentLoaded', initKanbanSortable);
document.addEventListener('htmx:afterSwap', initKanbanSortable);
</script>
{% endblock %}