Home > Enterprise >  How to make AlpineJS "re-render" component after HTMX swap
How to make AlpineJS "re-render" component after HTMX swap

Time:06-26

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}} }"
  • Related