Can you make sortable.js work with boostrap 5 modal?

I'm building a checklist app for fun and I'm trying to use sortable.js with python django.

I can make a sortable list work in this example with the html as follows


    {% extends 'BJJApp/base.html' %}
    {% load static %}
    {%load crispy_forms_tags %} 
    {% block content %}

    <br><br>

    <div id="standalone-items-container">
        {% for item, formset, links in standalone_items_formsets_links %}
        <div class="modal fade" id="exampleModalToggle-{{ item.id }}" aria-hidden="true" aria-labelledby="exampleModalToggleLabel-{{ item.id }}" data-item-id="{{ item.id }}" tabindex="-1">
            <div class="modal-dialog modal-dialog-centered">
                <div class="modal-content">
                    <div class="modal-header">
                        <h1 class="modal-title fs-5" id="exampleModalToggleLabel-{{ item.id }}" style="color: {% if item.important %}red{% else %}inherit{% endif %};">{{ item.title }}</h1>
                        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                    </div>
                    <div class="modal-body">
                        <form method="POST" id="main-form-{{ item.id }}" action="{% url 'viewitem' item.id %}">
                            {% csrf_token %}
                            <div class="form-group">
                                <label for="title">Title</label>
                                <input type="text" name="title" class="form-control" id="title-{{ item.id }}" value="{{ item.title }}" required disabled>
                            </div>
                            <div class="form-group">
                                <label for="memo">Memo</label>
                                <textarea name="memo" rows="5" class="form-control" id="memo-{{ item.id }}" disabled>{{ item.memo }}</textarea>
                            </div>
        
                            
                            
                            <div class="form-group form-check">
                                <input type="checkbox" name="important" class="form-check-input" id="important-{{ item.id }}" {% if item.important %}checked{% endif %} disabled>
                                <label class="form-check-label" for="important">Important</label>
                            </div>
                        </form>
                    </div>
        
                    <div id="links-{{ item.id }}">
                        {% if links %}
                            <ul>
                                {% for link in links %}
                                    <li><a href="{{ link.url }}" target="_blank">{{ link.url|urlizetrunc:50 }}</a></li>
                                {% endfor %}
                            </ul>
                        {% else %}
                            <p>&nbsp;&nbsp;&nbsp;&nbsp;No links available for this item.</p>
                        {% endif %}
                    </div>
                    <div class="d-flex justify-content-end">
                        <a href="{% url 'updatelinks' item.id %}" style="display: none" id="updatelinks-{{ item.id }}">
                            <button type="button" class="btn btn-warning me-5">
                                Add or Remove Links
                            </button>
                        </a>
                    </div>
                    <br>
                    <div class="modal-footer" >
        
                        <button type="button" id="edit-button-{{ item.id }}" class="btn btn-primary me-2" onclick="toggleEdit({{ item.id }})">Edit</button>
                        <!-- Complete Button Form (if item is not completed) -->
                        {% if item.datecompleted is None %}
                        <form method="POST" action="{% url 'completeitem' item.id %}" style="display:inline-block;">
                            {% csrf_token %}
                            <button type="submit" class="btn btn-success me-2">Complete</button>
                        </form>
                        {% endif %}
        
                        <!-- UnComplete Button Form (if item is completed) -->
                        {% if item.datecompleted %}
                        <form method="POST" action="{% url 'uncompleteitem' item.id %}" style="display:inline-block;">
                            {% csrf_token %}
                            <button type="submit" class="btn btn-success me-2">UnComplete</button>
                        </form>
                        {% endif %}
        
                        <!-- Delete Button Form -->
                        <form method="POST" action="{% url 'deleteitem' item.id %}" style="display:inline-block;">
                            {% csrf_token %}
                            <button type="submit" class="btn btn-danger">Delete</button>
                        </form>
        
                    </div>
                    
                </div>
            </div>
        </div>
        
        <!-- 
        <button class="btn btn-primary" id="exampleModalToggleButton-{{item.id}}" data-bs-target="#exampleModalToggle-{{ item.id }}" data-bs-toggle="modal" style="color: {% if item.important %}red{% else %}inherit{% endif %};" onclick="storeModalId('{{ item.id }}')">
            {{ item.title }}
        </button> -->
        
        <div class="card mb-3" style="max-width: 800px;" draggable="true" data-item-id="{{ item.id }}">
            <div class="card-body d-flex justify-content-between align-items-center"  style="cursor: pointer;">
            <!-- <div class="card-body d-flex justify-content-between align-items-center" data-bs-target="#exampleModalToggle-{{ item.id }}" data-bs-toggle="modal"  onclick="storeReferrerAndModal('{{ item.id }}', false)"  style="cursor: pointer;"> -->
                <!-- Card Content -->
                <div>
                    <h5 class="card-title" id="card-title-{{ item.id }}" style="color: {% if item.important %}red{% else %}inherit{% endif %};" >{{ forloop.counter }}. {{ item.title }}</h5>
                    <p class="card-text">{{ item.memo }}</p>
                </div>
        
                <!-- Buttons -->
                <div>
                    
                <button class="btn btn-primary" id="exampleModalToggleButton-{{item.id}}" data-bs-target="#exampleModalToggle-{{ item.id }}" data-bs-toggle="modal"  onclick="storeReferrerAndModal('{{ item.id }}', false)">
                    Details
                </button>
                    <!-- Complete Button Form (if item is not completed) -->
                    {% if item.datecompleted is None %}
                    <form method="POST" action="{% url 'completeitem' item.id %}" style="display:inline-block;">
                        {% csrf_token %}
                        <button type="submit" class="btn btn-success">Complete</button>
                    </form>
                    {% endif %}
        
                    <!-- UnComplete Button Form (if item is completed) -->
                    {% if item.datecompleted %}
                    <form method="POST" action="{% url 'uncompleteitem' item.id %}" style="display:inline-block;">
                        {% csrf_token %}
                        <button type="submit" class="btn btn-success">UnComplete</button>
                    </form>
                    {% endif %}
        
                    <!-- Delete Button Form -->
                    <form method="POST" action="{% url 'deleteitem' item.id %}" style="display:inline-block;">
                        {% csrf_token %}
                        <button type="submit" class="btn btn-danger">Delete</button>
                    </form>
                </div>
            </div>
        </div>
        
        
        
        
        
        {% endfor %}
        </div>



    {% endblock %}

    {% block scripts %}
    <script src="{% static 'Checklist/Checklist.js' %}" ></script>
    <script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
    <script>
        document.addEventListener('DOMContentLoaded', function () {
            const container = document.getElementById('standalone-items-container');
            const csrfToken = '{{ csrf_token }}'; // CSRF token for secure POST requests

            Sortable.create(container, {
                animation: 150, // Smooth animation while dragging
                onEnd: function (event) {
                    // Get the updated order of item IDs
                    const updatedOrder = Array.from(container.children).map((card, index) => {
                        // Update the displayed order on the card
                        const titleElement = card.querySelector('.card-title');
                        titleElement.textContent = `${index + 1}. ${titleElement.textContent.split('. ').slice(1).join('. ')}`;
                        return card.dataset.itemId;
                    });

                    // Send the updated order to the backend
                    fetch("{% url 'update_item_order' %}", {
                        method: "POST",
                        headers: {
                            "Content-Type": "application/json",
                            "X-CSRFToken": csrfToken, // CSRF token for Django
                        },
                        body: JSON.stringify({ order: updatedOrder }),
                    })
                    .then(response => {
                        if (!response.ok) {
                            throw new Error("Failed to update order.");
                        }
                        return response.json();
                    })
                    .then(data => {
                        console.log("Order updated:", data);
                    })
                    .catch(error => {
                        console.error("Error updating order:", error);
                    });
              },
            });
        });
    </script>



    {% endblock %}

in my views I have

def test5(request):
    items = Item.objects.filter(user=request.user, datecompleted__isnull=True)
    if request.user.profile.role == "instructor":
        courses = request.user.checklist_courses.filter(related_course__isnull=False)
    else:
        courses = request.user.checklist_courses.exclude(
            creator__profile__role="instructor"
        )
    courses_percentages = []
    standalone_items_formsets_links = []
    course_items_formsets_links = []
    standalone_items = items.filter(courses__isnull=True).order_by("order")
    course_items = items.filter(courses__isnull=False)

    for item in standalone_items:
        LanguageFormSet = inlineformset_factory(Item, Link, fields=("url",), extra=1)
        formset = LanguageFormSet(instance=item)
        links = Link.objects.filter(item=item)
        standalone_items_formsets_links.append((item, formset, links))

    for item in course_items:
        LanguageFormSet = inlineformset_factory(Item, Link, fields=("url",), extra=1)
        formset = LanguageFormSet(instance=item)
        links = Link.objects.filter(item=item)
        course_items_formsets_links.append((item, formset, links))

    for course in courses:
        total_items = course.items.count()
        completed_items = course.items.filter(datecompleted__isnull=False).count()
        # Avoid division by zero
        if total_items > 0:
            progress_percentage = (completed_items / total_items) * 100
        else:
            progress_percentage = 0
        courses_percentages.append((course, progress_percentage))

    return render(
        request,
        "BJJApp/test5.html",
        {
            "standalone_items": standalone_items,
            "courses_percentages": courses_percentages,
            "standalone_items_formsets_links": standalone_items_formsets_links,
            "course_items_formsets_links": course_items_formsets_links,
        },
    )

def update_item_order(request):
    if request.method == "POST":
        try:
            data = json.loads(request.body)
            item_ids = data.get("order", [])

            # Update the order field for each item
            for idx, item_id in enumerate(item_ids, start=1):
                Item.objects.filter(id=item_id).update(order=idx)

            return JsonResponse({"success": True})
        except Exception as e:
            return JsonResponse({"success": False, "error": str(e)}, status=400)

    return JsonResponse(
        {"success": False, "error": "Invalid request method."}, status=405
    )

this works fine and I can drag and drop and update the order number and display the updated number of the items in the card.

but when I change it to modal, it doesn't work and doesn't update. Can anyone help?

{% extends 'BJJApp/base.html' %}
{% load static %}
{%load crispy_forms_tags %} 
{% block content %}

<br><br>

<div id="standalone-items-container">

    {% for item, formset, links in standalone_items_formsets_links %}
    <div class="modal fade" id="exampleModalToggle-{{ item.id }}" aria-hidden="true" aria-labelledby="exampleModalToggleLabel-{{ item.id }}" data-item-id="{{ item.id }}" tabindex="-1">
        <div class="modal-dialog modal-dialog-centered">
            <div class="modal-content">
                <div class="modal-header">
                    <h1 class="modal-title fs-5" id="exampleModalToggleLabel-{{ item.id }}" style="color: {% if item.important %}red{% else %}inherit{% endif %};">{{ item.title }}</h1>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    <form method="POST" id="main-form-{{ item.id }}" action="{% url 'viewitem' item.id %}">
                        {% csrf_token %}
                        <div class="form-group">
                            <label for="title">Title</label>
                            <input type="text" name="title" class="form-control" id="title-{{ item.id }}" value="{{ item.title }}" required disabled>
                        </div>
                        <div class="form-group">
                            <label for="memo">Memo</label>
                            <textarea name="memo" rows="5" class="form-control" id="memo-{{ item.id }}" disabled>{{ item.memo }}</textarea>
                        </div>
    
                        
                        
                        <div class="form-group form-check">
                            <input type="checkbox" name="important" class="form-check-input" id="important-{{ item.id }}" {% if item.important %}checked{% endif %} disabled>
                            <label class="form-check-label" for="important">Important</label>
                        </div>
                    </form>
                </div>
    
                <div id="links-{{ item.id }}">
                    {% if links %}
                        <ul>
                            {% for link in links %}
                                <li><a href="{{ link.url }}" target="_blank">{{ link.url|urlizetrunc:50 }}</a></li>
                            {% endfor %}
                        </ul>
                    {% else %}
                        <p>&nbsp;&nbsp;&nbsp;&nbsp;No links available for this item.</p>
                    {% endif %}
                </div>
                <div class="d-flex justify-content-end">
                    <a href="{% url 'updatelinks' item.id %}" style="display: none" id="updatelinks-{{ item.id }}">
                        <button type="button" class="btn btn-warning me-5">
                            Add or Remove Links
                        </button>
                    </a>
                </div>
                <br>
                <div class="modal-footer" >
    
                    <button type="button" id="edit-button-{{ item.id }}" class="btn btn-primary me-2" onclick="toggleEdit({{ item.id }})">Edit</button>
                    <!-- Complete Button Form (if item is not completed) -->
                    {% if item.datecompleted is None %}
                    <form method="POST" action="{% url 'completeitem' item.id %}" style="display:inline-block;">
                        {% csrf_token %}
                        <button type="submit" class="btn btn-success me-2">Complete</button>
                    </form>
                    {% endif %}
    
                    <!-- UnComplete Button Form (if item is completed) -->
                    {% if item.datecompleted %}
                    <form method="POST" action="{% url 'uncompleteitem' item.id %}" style="display:inline-block;">
                        {% csrf_token %}
                        <button type="submit" class="btn btn-success me-2">UnComplete</button>
                    </form>
                    {% endif %}
    
                    <!-- Delete Button Form -->
                    <form method="POST" action="{% url 'deleteitem' item.id %}" style="display:inline-block;">
                        {% csrf_token %}
                        <button type="submit" class="btn btn-danger">Delete</button>
                    </form>
    
                </div>
                
            </div>
        </div>
    </div>
    
    <!-- 
    <button class="btn btn-primary" id="exampleModalToggleButton-{{item.id}}" data-bs-target="#exampleModalToggle-{{ item.id }}" data-bs-toggle="modal" style="color: {% if item.important %}red{% else %}inherit{% endif %};" onclick="storeModalId('{{ item.id }}')">
        {{ item.title }}
    </button> -->
    
    <div class="card" style="max-width: 800px;" data-item-id="{{ item.id }}">
        <div class="card-body d-flex justify-content-between align-items-center" data-bs-target="#exampleModalToggle-{{ item.id }}" data-bs-toggle="modal"  onclick="storeReferrerAndModal('{{ item.id }}', false)"  style="cursor: pointer;">
            <!-- Card Content -->
            <div>
                <h5 class="card-title" id="card-title-{{ item.id }}" style="color: {% if item.important %}red{% else %}inherit{% endif %};" >{{ forloop.counter }}. {{ item.title }}</h5>
                <p class="card-text">{{ item.memo }}</p>
            </div>
    
            <!-- Buttons -->
            <div>
                
            <button class="btn btn-primary" id="exampleModalToggleButton-{{item.id}}" data-bs-target="#exampleModalToggle-{{ item.id }}" data-bs-toggle="modal"  onclick="storeReferrerAndModal('{{ item.id }}', false)">
                Details
            </button>
                <!-- Complete Button Form (if item is not completed) -->
                {% if item.datecompleted is None %}
                <form method="POST" action="{% url 'completeitem' item.id %}" style="display:inline-block;">
                    {% csrf_token %}
                    <button type="submit" class="btn btn-success">Complete</button>
                </form>
                {% endif %}
    
                <!-- UnComplete Button Form (if item is completed) -->
                {% if item.datecompleted %}
                <form method="POST" action="{% url 'uncompleteitem' item.id %}" style="display:inline-block;">
                    {% csrf_token %}
                    <button type="submit" class="btn btn-success">UnComplete</button>
                </form>
                {% endif %}
    
                <!-- Delete Button Form -->
                <form method="POST" action="{% url 'deleteitem' item.id %}" style="display:inline-block;">
                    {% csrf_token %}
                    <button type="submit" class="btn btn-danger">Delete</button>
                </form>
            </div>
        </div>
    </div>
    <br>
    
    
    
    
    {% endfor %}
    </div>



{% endblock %}

{% block scripts %}
<script src="{% static 'Checklist/Checklist.js' %}" ></script>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
<script>
    document.addEventListener('DOMContentLoaded', function () {
        const container = document.getElementById('standalone-items-container');
        const csrfToken = '{{ csrf_token }}'; // CSRF token for secure POST requests

        Sortable.create(container, {
            animation: 150, // Smooth animation while dragging
            onEnd: function (event) {
                // Get the updated order of item IDs
                const updatedOrder = Array.from(container.children).map((card, index) => {
                    // Update the displayed order on the card
                    const titleElement = card.querySelector('.card-title');
                    titleElement.textContent = `${index + 1}. ${titleElement.textContent.split('. ').slice(1).join('. ')}`;
                    return card.dataset.itemId;
                });

                // Send the updated order to the backend
                fetch("{% url 'update_item_order' %}", {
                    method: "POST",
                    headers: {
                        "Content-Type": "application/json",
                        "X-CSRFToken": csrfToken, // CSRF token for Django
                    },
                    body: JSON.stringify({ order: updatedOrder }),
                })
                .then(response => {
                    if (!response.ok) {
                        throw new Error("Failed to update order.");
                    }
                    return response.json();
                })
                .then(data => {
                    console.log("Order updated:", data);
                })
                .catch(error => {
                    console.error("Error updating order:", error);
                });
            },
        });
    });
</script>



{% endblock %}

Thanks

Вернуться на верх