0

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>
        
        
        <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"></div>

        <br />
        <div class="modal-footer"></div>
      </div>
    </div>
  </div>
  <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"
    >
      <!-- 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>
    </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 %}

Thanks

1
  • 1
    Thanks, I've edited it to remove a lot of the extra extraneous code. Commented Jan 16 at 6:23

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.