I am trying to implement a drag and drop sortable list using SortableJS and htmx. I have it working once, but after dragging and dropping the first element (and the partial being re rendered) I can no longer use the drag and drop functionality. When a partial isn't rerendered the drag and drop functionality works as expected. I have tried using htmx.on("htmx:load",... as well as putting the script in the partial.
I've used diff to check the differences between the html before and after the partial is rendered and as far as I can tell the only difference outside the reordered list is the csrf token.
Any help would be appreciated!
From views.py:
def sort(request):
event_pks_order = request.POST.getlist('event_order')
events=[]
for idx,event_pk in enumerate(event_pks_order,start=1):
event = Event.objects.get(pk=event_pk)
event.event_number = idx
event.save()
events.append(event)
return render(request,'timing/partials/eventlist.html',{'events':events})
From eventlist.html:
<form class="sortable list-group" hx-trigger="end" hx-post="{% url 'sort' %}" hx-target="#event-list">
{% csrf_token %}
<div class="htmx-indicator">Updating...</div>
{% for event in events %}
<div>
<input type="hidden" name="event_order" value="{{event.pk}}"/>
<li class="list-group-item">{{event.event_number}} {{event.event_name}}
</li>
</div>
{% endfor %}
</form>
From base.html:
<script>
document.body.addEventListener('htmx:configRequest', (event) => {
event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
})
htmx.onLoad(function(content) {
var sortables = content.querySelectorAll(".sortable");
for (var i = 0; i < sortables.length; i++) {
var sortable = sortables[i];
new Sortable(sortable, {
animation: 150,
ghostClass: 'blue-background-class'
});
}
})
</script>
I think SortableJS does some initialization to the HTML.
So it works fine for the first time, but the new HTML which gets swapped in by htmx does not get initialized.
This is a common pitfall.
By chance the use the same example like you do: sortable :-)
Here you have the docs about it: https://htmx.org/docs/#3rd-party
In htmx, you would instead use the htmx.onLoad function, and you would select only from the newly loaded content, rather than the entire document:
htmx.onLoad(function(content) {
var sortables = content.querySelectorAll(".sortable");
for (var i = 0; i < sortables.length; i++) {
var sortable = sortables[i];
new Sortable(sortable, {
animation: 150,
ghostClass: 'blue-background-class'
});
}
})
This will ensure that as new content is added to the DOM by htmx, sortable elements are properly initialized.
Does this solve your question?
Has this been cleared?
I faced the same issue.
I solved this problem by changing the target template file to render of sort function in views.py.
So to speak, you seem to need to change this line.
return render(request,'timing/partials/eventlist.html',{'events':events})
You have a comment "Where is the element having event-list id that you set in hx-target?" –by Mr./Mrs. Dauros
I can't comment because I need more than 49 reputations to comment.
But I predict that your template file "eventlist.html" may be included by another template HTML file using {% include %} tag judging from your source code being similar to BugBytes HTMX and Django tutorial on YouTube and using hx-target within the form tag.
The repository of that course here afresh.
I made a post to Stackoverflow about this same issue, so please refer to it.
Link is below.
HTMX sortable works only once, rendering makes it disabled
I hope this answer is helpful for your matter and other future students! :)