In my WooCommerce theme, I'm want to show the spinner icon when WooCommerce dynamically adds the "loading" class to the element using AJAX.
I tried to use Alpine.js's $el property to retrieve the current DOM node, but this isn't working. It's also not 'watching' the classList of .
How can I accomplish this using Alpine.js?
<button type="submit" name="add-to-cart" value="<?php echo esc_attr( $product->get_id() ); ?>" class="ajax_add_to_cart add_to_cart_button single_add_to_cart_button button alt flex justify-center" data-product_id="<?php echo get_the_ID(); ?>">
<!-- Spinner Icon -->
<svg x-show="$el.parentElement.classList.contains('loading')" class="animate-spin py-1 h-7 w-7 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
...
</svg>
<!-- Toevoegen aan winkelmand -->
<span x-show="$el.parentElement.classList.contains('!loading')">
<?php echo esc_html( $product->single_add_to_cart_text() ); ?>
</span>
</button>
Digging into WooCommerce source code revealed that it uses jQuery event system so we have to create a little event bus between jQuery and Alpine.js. The two respective events are adding_to_cart that called before the AJAX call and the added_to_cart event that triggered after the successful AJAX call (i.e. the product has been added to the cart).
Let's call our event bus catchWooEvents:
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('catchWooEvents', () => ({
loading: false,
init() {
$(document.body).on('adding_to_cart', (event, button, data) => {
this.loading = true
})
$(document.body).on('added_to_cart', (fragments, cart_hash, button) => {
this.loading = false
})
}
}))
})
</script>
We have a new variable loading that is active between the two events. You see that we used the jQuery's $.on() function inside the init() to catch the jQuery events, but then we manipulate an Alpine.js variable.
The modified button example:
<div x-data="catchWooEvents">
<button type="submit" name="add-to-cart" value="<?php echo esc_attr( $product->get_id() ); ?>" class="ajax_add_to_cart add_to_cart_button single_add_to_cart_button button alt flex justify-center" data-product_id="<?php echo get_the_ID(); ?>">
<!-- Spinner Icon -->
<svg x-show="loading" class="animate-spin py-1 h-7 w-7 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
...
</svg>
<!-- Toevoegen aan winkelmand -->
<span x-show="!loading">
<?php echo esc_html( $product->single_add_to_cart_text() ); ?>
</span>
</button>
</div>
We have a new parent div element where we applied the catchWooEvents component, so multiple child buttons can share the loading state. In the x-show attribute the loading variable is now reactive.
Note: the definition of catchWooEvents must be placed after the jQuery script line.