Home > Back-end >  Can I Reorder a LIst Based On User Provided Values Using Only HTMX?
Can I Reorder a LIst Based On User Provided Values Using Only HTMX?

Time:02-23

I have HTMX working. The code below is fully functional. The one piece I'd like to incorporate, I can't figure out how to do it. The user is able to provide a number rank...but when they click save, the view returns with the item at the top. Only after they click reload does the list sort itself based on how I defined the attributes with the model.

Here's my HTML...

<h1 >Tasks</h1>

<button  hx-get="{% url 'MyTasks:create_task_form' %}" hx-target="#taskforms">Add Task</button>

<div id="taskforms"></div>

<div id="tblData">

{% if tasks %}

  {% for task in tasks %}

    {% include "partials/task_detail.html" %}

  {% endfor %}

</div>

{% endif %}

<div hx-target="this" hx-swap="outerHTML" hx-headers='{"X-CSRFToken":"{{ csrf_token }}"}'  >
  <form method="POST">
    {% csrf_token %}

      <div >
            <table >
              <thead>
                <tr>
                  <th >Number</th>  
                  <th >Task</th>  
                </tr>
              </thead>
              <tbody>
                <button  hx-post=".">
                  Save
                </button>
                <button type="button" >
                  Delete
                </button>
                <tr> 
                    <td >{{ form.number }}</td>
                    <td >{{ form.task }}</td>
                  </tr>
                </tbody>
             </table> 
          </div>
  </form>
</div>

<script>
    document.body.addEventListener('htmx:configRequest', (event) => {
        event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
    })
</script>

<div hx-target="this" >

  <div >
        <table >
          <thead>
            <tr>
               <th >Number</th> 
               <th >Task</th> 
            </tr>
          </thead>
          <tbody>
            <button  hx-get="{% url 'MyTasks:update_task' task.id %}" hx-swap="outerHTML">
                Update
            </button>
             <button  hx-confirm="Are you sure you want to delete?" hx-post="{% url 'MyTasks:delete_task' task.id %}" hx-swap="outerHTML">
                Delete
            </button>
            <tr>
                 <td >{{ task.number }}</td>  
                 <td >{{ task.task }}</td>  
              </tr>
            </tbody>
         </table>
       </div>
    </div>

My Model...

class Task(models.Model):

    task = models.TextField(max_length=264,blank=True,null=True,unique=False)
    number = models.PositiveIntegerField(default=1) 

    class Meta:
        ordering = ["number"]

    def __str__(self):
        return self.task

I have begun to explore using Javascript to do HTML sorting to approach my issue that way instead. It just seems to me as capable as HTMX is there should be a way for me to do it leveraging HTMX. Thanks in advance for any thoughts.

CodePudding user response:

I think the easiest way would be just to swap in the entire list on save instead of just the new item - that way you can take advantage of the ordering in the model.

EDIT: In order to swap a list of the task in instead of the saved task, you will need to modify your view to return a list of the tasks instead of just the task being saved - like this for a CreateView:

class TaskCreateView(CreateView):
    model = Task
    form_class = TaskForm
    template_name = 'task/_create_form.html'

    def form_valid(self, form):
        self.object = form.save()
        tasks = Task.objects.all()
        return render(self.request, 'task/_task_list.html', {'tasks': tasks})

You would also need to make partial template that would render all the tasks - something like this:

<div id="tblData">
{% if tasks %}
    <ul>
        {% for task in tasks %}
            <li>{{ task.order }} - {{ task.task }}</li>
        {% endfor %}
    </ul>
{% endif %}

CodePudding user response:

The HTMX technique you are looking for is the Out of Band Swapping which is basically 1 request with multiple targets. The targets can be anywhere on the page. In your case: when the user creates, changes or deletes a task, the response should also contain the updated list of tasks. It requires only a few modifications. For example a task updating view:

def update_view(request):
    tasks_updated = False
    tasks = None

    if request.method == 'POST':
        form = TaskForm(request.POST)
        if form.is_valid():
           # Update the task in the database
           ...
           task.save()

           tasks_updated = True
        else:
           # Return the form with errors
           return render(request, 'task_form.html', {'form': form})

    if tasks_updated:
        # Fetch new list of tasks
        tasks = Task.objects.order_by('number')

    return render(request, 'task.html', {'tasks': tasks, 'task': task})

We have the tasks_updated tracking variable that we switch to True if we updated a task in the database, so the task list needs an update on the frontend. Just before we render the template, we check the value of this variable and fetch the tasks if needed.

And the partial template:

<div>
<!-- Render the task's table as usual. -->
</div>

{% if tasks %}
<div id="tblData" hx-swap-oob="true">
  {% for task in tasks %}
    {% include "partials/task_detail.html" %}
  {% endfor %}
</div>
{% endif %}

Here we render the table only we have the tasks variable, so at least one of the task was updated therefore we loaded the tasks as well. The hx-swap-oob="true" tells HTMX to swap the element having tblData id.

Basically that's it. Just include an OOB-Swap task list in each response, where the task list needs an update. If you load the "new task form" you don't need it, but if you add/update/delete a task, you need a fresh OOB-Swap task list in the response (fetched from the database after the task operation has been finished).

  • Related