Skip to content

Commit 71f67dd

Browse files
committed
drag and drop option
1 parent 0ef19a7 commit 71f67dd

File tree

3 files changed

+120
-56
lines changed

3 files changed

+120
-56
lines changed

src/cs_dynamicpages/browser/static/dynamicpageview.css

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,30 @@ body.template-dynamic-view.can_edit {
7272
height: 5em;
7373
width: auto;
7474
}
75+
76+
/* Estiloa heldulekuarentzat (handle) */
77+
.drag-handle {
78+
cursor: grab;
79+
color: #aaa;
80+
margin-right: 10px;
81+
}
82+
83+
.drag-handle:hover {
84+
color: #333;
85+
}
86+
87+
/* SortableJS-k erabiltzen dituen klaseak */
88+
.sortable-ghost {
89+
opacity: 0 !important;
90+
background-color: #c8ebfb !important;
91+
border: 2px dashed var(--dynamicpages-primary-color);
92+
}
93+
94+
.sortable-chosen,
95+
.sortable-drag {
96+
opacity: 1 !important;
97+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
98+
}
7599
}
76100

77101
.no-underline {
Lines changed: 87 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
/**
22
* Handles reordering of dynamic page rows
33
*/
4-
54
(function () {
65
"use strict";
76

7+
// Store references to avoid re-querying the DOM
8+
let sortableInstance = null;
9+
const upClickHandlers = new Map();
10+
const downClickHandlers = new Map();
11+
let isInitialized = false;
12+
813
// Initialize when DOM is loaded
914
if (document.readyState === "loading") {
1015
document.addEventListener("DOMContentLoaded", initRowReordering);
@@ -13,76 +18,106 @@
1318
}
1419

1520
function initRowReordering() {
16-
// Only run on dynamic-view with edit permissions
1721
if (
1822
!document.body.classList.contains("template-dynamic-view") ||
1923
!document.body.classList.contains("can_edit")
2024
) {
2125
return;
2226
}
2327

28+
// Initialize drag-and-drop reordering
29+
initDragAndDropReordering();
30+
2431
// Find all move up/down buttons
2532
const moveUpButtons = document.querySelectorAll('a[data-action="move-up"]');
2633
const moveDownButtons = document.querySelectorAll(
2734
'a[data-action="move-down"]'
2835
);
2936

30-
// Add event listeners to move up buttons
3137
moveUpButtons.forEach((button) => {
32-
button.addEventListener(
33-
"click",
34-
function (e) {
35-
e.preventDefault();
36-
e.stopPropagation(); // Detener la propagación del evento
37-
38-
// Deshabilitar el botón temporalmente
39-
if (this.disabled) return;
40-
this.disabled = true;
41-
42-
const element = this.closest('[data-move-target="true"]');
43-
44-
// Re-habilitar el botón después de un tiempo
45-
setTimeout(() => {
46-
this.disabled = false;
47-
}, 2000);
48-
49-
moveElement(element, -1);
50-
},
51-
{ once: true }
52-
); // Usar { once: true } para que el listener se ejecute solo una vez
38+
button.addEventListener("click", (e) => handleButtonClick(e, -1));
5339
});
5440

55-
// Add event listeners to move down buttons
5641
moveDownButtons.forEach((button) => {
57-
button.addEventListener(
58-
"click",
59-
function (e) {
60-
e.preventDefault();
61-
e.stopPropagation(); // Detener la propagación del evento
62-
63-
// Deshabilitar el botón temporalmente
64-
if (this.disabled) return;
65-
this.disabled = true;
66-
67-
const element = this.closest('[data-move-target="true"]');
68-
69-
// Re-habilitar el botón después de un tiempo
70-
setTimeout(() => {
71-
this.disabled = false;
72-
}, 2000);
73-
74-
moveElement(element, 1);
75-
},
76-
{ once: true }
77-
); // Usar { once: true } para que el listener se ejecute solo una vez
42+
button.addEventListener("click", (e) => handleButtonClick(e, 1));
7843
});
7944
}
8045

81-
function moveElement(element, delta) {
46+
function handleButtonClick(e, delta) {
47+
e.preventDefault();
48+
e.stopPropagation();
49+
50+
const button = e.currentTarget;
51+
if (button.disabled) return;
52+
button.disabled = true;
53+
54+
const element = button.closest('[data-move-target="true"]');
55+
if (element) {
56+
moveElementInDOM(element, delta);
57+
sendReorderRequest(element, delta);
58+
}
59+
60+
setTimeout(() => (button.disabled = false), 500);
61+
}
62+
63+
function initDragAndDropReordering() {
64+
if (typeof Sortable === "undefined") {
65+
console.log("SortableJS not loaded. Drag-and-drop reordering disabled.");
66+
return;
67+
}
68+
69+
// Find the container of the rows. We assume all draggable items share the same parent.
70+
const firstDraggableElement = document.querySelector(
71+
'[data-move-target="true"]'
72+
);
73+
if (!firstDraggableElement?.parentElement) {
74+
return;
75+
}
76+
const container = firstDraggableElement.parentElement;
77+
78+
// Add a class for styling the handle
79+
container.classList.add("sortable-container");
80+
81+
// Initialize SortableJS
82+
new Sortable(container, {
83+
animation: 150, // ms, animation speed moving items when sorting, `0` — without animation
84+
handle: ".drag-handle", // Restrict drag start to elements with this class
85+
ghostClass: "sortable-ghost", // Class for the drop placeholder
86+
chosenClass: "sortable-chosen", // Class for the chosen item
87+
dragClass: "sortable-drag", // Class for the dragging item
88+
89+
// Element is dropped
90+
onEnd: (evt) => {
91+
const { oldIndex, newIndex, item } = evt;
92+
if (oldIndex !== newIndex) {
93+
const delta = newIndex - oldIndex;
94+
sendReorderRequest(item, delta);
95+
}
96+
},
97+
});
98+
}
99+
100+
function moveElementInDOM(element, delta) {
101+
const parent = element.parentNode;
102+
if (!parent) return;
103+
104+
if (delta > 0) {
105+
// Move down
106+
const nextTarget = element.nextElementSibling?.nextElementSibling || null;
107+
parent.insertBefore(element, nextTarget);
108+
} else {
109+
// Move up
110+
const target = element.previousElementSibling;
111+
if (target) {
112+
parent.insertBefore(element, target);
113+
}
114+
}
115+
}
116+
117+
function sendReorderRequest(element, delta) {
82118
const elementId = element.dataset.elementid;
83119
if (!elementId) {
84-
const errorMsg = "No data-element-id attribute found on element";
85-
alert(errorMsg);
120+
console.error("No data-element-id attribute found on element");
86121
return;
87122
}
88123

@@ -111,13 +146,9 @@
111146
throw error;
112147
}
113148
})
114-
.finally(() => {
115-
sessionStorage.setItem(
116-
"toast-message",
117-
"Element reordered successfully."
118-
);
119-
// Refresh the page after successful update
120-
window.location.reload();
149+
.catch((error) => {
150+
console.error("Error reordering element:", error);
151+
alert("Error reordering element. Please refresh the page.");
121152
});
122153
}
123154
})();

src/cs_dynamicpages/views/dynamic_view.pt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
</div>
1919
</div>
2020
</div>
21+
<tal:conditiondrapanddrop tal:condition="view/can_edit">
22+
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
23+
</tal:conditiondrapanddrop>
2124
<tal:features define="
2225
rows view/rows;
2326
">
@@ -27,6 +30,12 @@
2730
id="${row/id}" data-delete-target="true" data-elementid="${row/id}" data-elementurl="${row/absolute_url}"
2831
data-move-target="true" data-parenturl="${row/aq_parent/absolute_url}" tal:define="row brain/getObject;">
2932
<div class="gap-2 edit-options edit-buttons d-flex" tal:condition="row/can_edit">
33+
<div class="drag-handle" title="Drap and drop">
34+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
35+
fill="currentColor">
36+
<path d="M10 4H4v6h6V4zm0 10H4v6h6v-6zm10-10h-6v6h6V4zm0 10h-6v6h6v-6z" />
37+
</svg>
38+
</div>
3039
<small
3140
class="px-2 py-1 mb-3 border d-inline-flex align-items-center fw-semibold text-warning-emphasis bg-warning-subtle border-warning-subtle rounded-2 position-relative">
3241
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-info-circle" fill="currentColor" height="16"

0 commit comments

Comments
 (0)