Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can you make sortable.js work with Bootstrap 5 modal? #2419

Open
Ystan21 opened this issue Jan 16, 2025 · 0 comments
Open

Can you make sortable.js work with Bootstrap 5 modal? #2419

Ystan21 opened this issue Jan 16, 2025 · 0 comments

Comments

@Ystan21
Copy link

Ystan21 commented Jan 16, 2025

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 %}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant