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).