Files
CommerceKit-FloatingCart/assets/js/floating-cart.js

225 lines
6.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* CommerceKit Floating Cart frontend script
*
* Responsibilities:
* 1. Open the Shoptimizer minicart when the floating button is clicked.
* 2. Intercept single-product add-to-cart form submits, send them via AJAX,
* then open the minicart (WooCommerce's built-in AJAX only covers archives).
* 3. Auto-open the minicart after AJAX add-to-cart on archive/shop pages.
* 4. Animate the item-count badge when the cart changes.
*/
( function ( $ ) {
'use strict';
var FloatingCart = {
$btn: null,
$count: null,
prevCount: 0,
init: function () {
if ( typeof cgkitFC === 'undefined' ) {
return;
}
this.$btn = $( '#cgkit-floating-cart .cgkit-floating-cart__btn' );
this.$count = $( '#cgkit-fc-count' );
if ( ! this.$btn.length ) {
return;
}
this.prevCount = parseInt( this.$count.text(), 10 ) || 0;
this.bindFloatingButton();
this.bindArchiveAddToCart();
this.bindSingleProductAddToCart();
this.bindFragmentRefresh();
},
// -----------------------------------------------------------------
// 1. Floating button click → open minicart
// -----------------------------------------------------------------
bindFloatingButton: function () {
var self = this;
this.$btn.on( 'click', function ( e ) {
e.preventDefault();
// Stop bubbling to Shoptimizer's document-level "click outside = close" handler.
e.stopPropagation();
self.openMinicart();
} );
},
// -----------------------------------------------------------------
// 2. Archive / shop pages WooCommerce fires `added_to_cart` natively
// after its own AJAX add-to-cart (enabled via PHP filter).
// -----------------------------------------------------------------
bindArchiveAddToCart: function () {
if ( cgkitFC.autoOpen !== 'yes' ) {
return;
}
var self = this;
$( document.body ).on( 'added_to_cart', function ( e, fragments, cartHash, $btn ) {
// Skip if this came from our own single-product handler
// (it will open the cart itself after the AJAX call).
if ( $btn && $btn.data( 'cgkit-fc-handled' ) ) {
return;
}
setTimeout( function () {
self.openMinicart();
self.pulseBtn();
}, parseInt( cgkitFC.autoOpenDelay, 10 ) || 400 );
} );
},
// -----------------------------------------------------------------
// 3. Single product page intercept form submit and use AJAX.
//
// WooCommerce's `woocommerce_enable_ajax_add_to_cart` option only
// covers archive loop buttons, NOT the single-product form. We handle
// it here so the page never reloads and the minicart opens instead.
// -----------------------------------------------------------------
bindSingleProductAddToCart: function () {
if ( cgkitFC.autoOpen !== 'yes' ) {
return;
}
var self = this;
var ajaxUrl = ( cgkitFC.wcAjaxUrl || '' ).replace( '%%endpoint%%', 'add_to_cart' );
if ( ! ajaxUrl ) {
return;
}
$( document ).on( 'submit', 'form.cart', function ( e ) {
var $form = $( this );
var $btn = $form.find( '.single_add_to_cart_button' );
// Skip disabled, loading, or variation-not-selected states.
if (
! $btn.length ||
$btn.hasClass( 'disabled' ) ||
$btn.hasClass( 'loading' ) ||
$btn.hasClass( 'wc-variation-selection-needed' )
) {
return;
}
// Skip grouped product forms (they POST to the cart page directly).
if ( $form.hasClass( 'grouped_form' ) ) {
return;
}
e.preventDefault();
$btn.addClass( 'loading' ).data( 'cgkit-fc-handled', true );
$.post( ajaxUrl, $form.serialize() )
.done( function ( response ) {
if ( ! response ) {
self.formFallback( $form, $btn );
return;
}
// WooCommerce signals a validation error by setting product_url.
if ( response.error && response.product_url ) {
window.location.href = response.product_url;
return;
}
// Let WooCommerce update all fragments (header count, mini-cart HTML, etc.)
$( document.body ).trigger( 'added_to_cart', [
response.fragments,
response.cart_hash,
$btn
] );
$btn.removeClass( 'loading' );
// Open the minicart after fragments have settled.
setTimeout( function () {
self.openMinicart();
self.pulseBtn();
}, parseInt( cgkitFC.autoOpenDelay, 10 ) || 400 );
} )
.fail( function () {
self.formFallback( $form, $btn );
} );
} );
},
// Fall back to a normal (non-AJAX) submit if the AJAX call fails.
formFallback: function ( $form, $btn ) {
$btn.removeClass( 'loading' ).removeData( 'cgkit-fc-handled' );
$( document ).off( 'submit', 'form.cart' );
$form.submit();
},
// -----------------------------------------------------------------
// 4. Badge animation when WooCommerce refreshes fragments
// -----------------------------------------------------------------
bindFragmentRefresh: function () {
var self = this;
$( document.body ).on( 'wc_fragments_refreshed wc_fragments_loaded', function () {
self.maybeBumpBadge();
} );
},
// -----------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------
/**
* Open the Shoptimizer minicart by clicking its header button.
* Uses ONE native click only — never both jQuery trigger and native
* click, which would double-fire and toggle the cart back closed.
* Stops propagation so Shoptimizer's document "click outside" handler
* does not immediately close the cart again.
*/
openMinicart: function () {
var selectors = ( cgkitFC.minicartTrigger || '' ).split( ',' );
for ( var i = 0; i < selectors.length; i++ ) {
var $el = $( $.trim( selectors[ i ] ) ).first();
if ( $el.length ) {
$el[ 0 ].click();
return;
}
}
// Absolute fallback: navigate to cart page.
if ( cgkitFC.cartUrl ) {
window.location.href = cgkitFC.cartUrl;
}
},
pulseBtn: function () {
var self = this;
this.$btn.addClass( 'cgkit-fc--pulse' );
setTimeout( function () {
self.$btn.removeClass( 'cgkit-fc--pulse' );
}, 600 );
},
maybeBumpBadge: function () {
var $fresh = $( '#cgkit-fc-count' );
if ( ! $fresh.length ) {
return;
}
this.$count = $fresh;
var newCount = parseInt( this.$count.text(), 10 ) || 0;
if ( newCount !== this.prevCount ) {
this.prevCount = newCount;
this.$count.removeClass( 'cgkit-floating-cart__count--bump' );
void this.$count[ 0 ].offsetWidth; // force reflow to restart animation
this.$count.addClass( 'cgkit-floating-cart__count--bump' );
}
}
};
$( document ).ready( function () {
FloatingCart.init();
} );
} )( jQuery );