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:
1 file changed
+42
-15
@@ -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 %}
|
||||
Reference in new issue
Block a user