I'm trying to integrate Django AlpineJS HTMX. All is going well, except when I try to swap some HTML that is already a part of a AlpineJS component.
If I swap the html the component renders correctly only after the second swap.
template
<div id="cart-row" x-data="{selectedOrderItem: {{item_id|default:'null'}} }">
<div id="cart-holder">
<table id="cart">
<thead>
<tr>
<th style="width: 10%">CANT</th>
<th>Product</th>
<th style="width: 15%">TOTAL</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr
id="product{{item.product.id}}"
:
@click="selectedOrderItem = {{item.id}};"
>
<td>{{item.quantity}}</td>
<td>{{item.product.name}}</td>
<td>{{item.get_price}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div id="right-menu">
<div id="table-buttons" hx-swap-obb="true">
{% for item in items %}
<div x-cloak x-show="selectedOrderItem == {{item.id}}">
<button
hx-trigger="click"
hx-target="#cart-row"
hx-swap="outerHTML"
hx-post="{% url 'add_to_cart' idOrder=order.id idProduct=item.product.id %}?cartAction=1"
>
{{item.id}}
</button>
</div>
{% endfor %}
</div>
</div>
For example, if I hit the add to cart button, the #cart-row
div is swapped, but the :
binding only works the second time. It's like AlpineJS doesn't realize that the content has been swapped.
Is there any way that I can make the whole component "re-render / re-initialize"? Or what is the right way to do that swap?
EDIT:
view
def add_to_cart(request, idOrder=None, idProduct=None):
try:
with transaction.atomic():
selectedOrderItem, created = OrderItem.objects.get_or_create(order_id=idOrder, product_id=idProduct, defaults={'quantity': 1})
if not created:
selectedOrderItem.quantity = 1
selectedOrderItem.save()
items = OrderItem.objects.select_related().filter(order_id=idOrder)
order = get_object_or_404(Order, pk=idOrder)
elements = []
# CART
elements.append(
render_to_string(
'rfidapp/partials/cart_row.html',
request=request,
context={
"order": order,
"items": items,
"selectedOrderItem": selectedOrderItem.id
}
)
)
#OOB SWAP TO UPDATE THE TOTAL AT THE BOTTOM LEFT
elements.append(
render_to_string('rfidapp/partials/order_total.html', request=request, context={"order": order})
)
return HttpResponse("\n".join(elements))
except Exception as e:
return HttpResponseBadRequest(f"{e}")
CodePudding user response:
The problem is that you are not including the one state data that this Alpine.js component is having (selectedOrderItem
) in the HTMX request. So when HTMX swaps the whole component, it will lose the value of selectedOrderItem
during the process. We need to send this variable to the backend and include it in the template. The easiest way to add additional data to the HTMX request is to use a hidden input element with x-model
on it to sync data, and then use hx-include
to add it to the request. Put this anywhere inside the component:
<input type="hidden" name="selected_order_item" x-model="selectedOrderItem">
And modify the button:
<button
hx-trigger="click"
hx-target="#cart-row"
hx-swap="outerHTML"
hx-include="[name='selected_order_item']"
hx-post="{% url 'add_to_cart' idOrder=order.id idProduct=item.product.id %}?cartAction=1"
>
At the backend now we can access it in request.POST
, e.g.:
context={
"order": order,
"items": items,
"selectedOrderItem": request.POST.get("selected_order_item", "null")
}
After that in the x-data
the selected order item will appear.
x-data="{selectedOrderItem: {{selectedOrderItem}} }"