2026-03-05 08:01:33 +01:00
|
|
|
|
/**
|
|
|
|
|
|
* CommerceKit Floating Cart – frontend script
|
|
|
|
|
|
*
|
|
|
|
|
|
* Responsibilities:
|
2026-03-05 08:38:04 +01:00
|
|
|
|
* 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.
|
2026-03-05 08:01:33 +01:00
|
|
|
|
*/
|
|
|
|
|
|
( function ( $ ) {
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
|
|
var FloatingCart = {
|
|
|
|
|
|
|
2026-03-05 08:38:04 +01:00
|
|
|
|
$btn: null,
|
|
|
|
|
|
$count: null,
|
2026-03-05 08:01:33 +01:00
|
|
|
|
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;
|
|
|
|
|
|
|
2026-03-05 08:38:04 +01:00
|
|
|
|
this.bindFloatingButton();
|
|
|
|
|
|
this.bindArchiveAddToCart();
|
|
|
|
|
|
this.bindSingleProductAddToCart();
|
|
|
|
|
|
this.bindFragmentRefresh();
|
2026-03-05 08:01:33 +01:00
|
|
|
|
},
|
|
|
|
|
|
|
2026-03-05 08:38:04 +01:00
|
|
|
|
// -----------------------------------------------------------------
|
|
|
|
|
|
// 1. Floating button click → open minicart
|
|
|
|
|
|
// -----------------------------------------------------------------
|
|
|
|
|
|
bindFloatingButton: function () {
|
2026-03-05 08:01:33 +01:00
|
|
|
|
var self = this;
|
|
|
|
|
|
this.$btn.on( 'click', function ( e ) {
|
|
|
|
|
|
e.preventDefault();
|
2026-03-05 08:38:04 +01:00
|
|
|
|
// Stop bubbling to Shoptimizer's document-level "click outside = close" handler.
|
2026-03-05 08:20:37 +01:00
|
|
|
|
e.stopPropagation();
|
2026-03-05 08:01:33 +01:00
|
|
|
|
self.openMinicart();
|
|
|
|
|
|
} );
|
2026-03-05 08:38:04 +01:00
|
|
|
|
},
|
2026-03-05 08:01:33 +01:00
|
|
|
|
|
2026-03-05 08:38:04 +01:00
|
|
|
|
// -----------------------------------------------------------------
|
|
|
|
|
|
// 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;
|
2026-03-05 08:01:33 +01:00
|
|
|
|
}
|
2026-03-05 08:38:04 +01:00
|
|
|
|
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 );
|
|
|
|
|
|
} );
|
|
|
|
|
|
},
|
2026-03-05 08:01:33 +01:00
|
|
|
|
|
2026-03-05 08:38:04 +01:00
|
|
|
|
// -----------------------------------------------------------------
|
|
|
|
|
|
// 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;
|
2026-03-05 08:01:33 +01:00
|
|
|
|
}
|
2026-03-05 08:38:04 +01:00
|
|
|
|
|
|
|
|
|
|
// 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 );
|
|
|
|
|
|
|
2026-03-05 08:44:52 +01:00
|
|
|
|
// WC's wc-ajax=add_to_cart reads $_POST['product_id'], but the
|
|
|
|
|
|
// single-product form submits the ID as field name 'add-to-cart'.
|
|
|
|
|
|
// Append the correctly-named field so the endpoint finds it.
|
|
|
|
|
|
var serialized = $form.serialize();
|
|
|
|
|
|
var productId = $form.find( '[name="add-to-cart"]' ).val() ||
|
|
|
|
|
|
$form.find( '[name="product_id"]' ).val() || '';
|
|
|
|
|
|
if ( productId && serialized.indexOf( 'product_id=' ) === -1 ) {
|
|
|
|
|
|
serialized += '&product_id=' + encodeURIComponent( productId );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$.post( ajaxUrl, serialized )
|
2026-03-05 08:38:04 +01:00
|
|
|
|
.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();
|
|
|
|
|
|
} );
|
2026-03-05 08:01:33 +01:00
|
|
|
|
},
|
|
|
|
|
|
|
2026-03-05 08:38:04 +01:00
|
|
|
|
// -----------------------------------------------------------------
|
|
|
|
|
|
// Helpers
|
|
|
|
|
|
// -----------------------------------------------------------------
|
|
|
|
|
|
|
2026-03-05 08:01:33 +01:00
|
|
|
|
/**
|
2026-03-05 08:38:04 +01:00
|
|
|
|
* 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.
|
2026-03-05 08:01:33 +01:00
|
|
|
|
*/
|
|
|
|
|
|
openMinicart: function () {
|
|
|
|
|
|
var selectors = ( cgkitFC.minicartTrigger || '' ).split( ',' );
|
|
|
|
|
|
|
|
|
|
|
|
for ( var i = 0; i < selectors.length; i++ ) {
|
|
|
|
|
|
var $el = $( $.trim( selectors[ i ] ) ).first();
|
|
|
|
|
|
if ( $el.length ) {
|
2026-03-05 08:08:18 +01:00
|
|
|
|
$el[ 0 ].click();
|
|
|
|
|
|
return;
|
2026-03-05 08:01:33 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-05 08:38:04 +01:00
|
|
|
|
// Absolute fallback: navigate to cart page.
|
2026-03-05 08:01:33 +01:00
|
|
|
|
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 () {
|
2026-03-05 08:38:04 +01:00
|
|
|
|
var $fresh = $( '#cgkit-fc-count' );
|
|
|
|
|
|
if ( ! $fresh.length ) {
|
2026-03-05 08:01:33 +01:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-05 08:38:04 +01:00
|
|
|
|
this.$count = $fresh;
|
2026-03-05 08:01:33 +01:00
|
|
|
|
|
|
|
|
|
|
var newCount = parseInt( this.$count.text(), 10 ) || 0;
|
|
|
|
|
|
if ( newCount !== this.prevCount ) {
|
|
|
|
|
|
this.prevCount = newCount;
|
|
|
|
|
|
this.$count.removeClass( 'cgkit-floating-cart__count--bump' );
|
2026-03-05 08:38:04 +01:00
|
|
|
|
void this.$count[ 0 ].offsetWidth; // force reflow to restart animation
|
2026-03-05 08:01:33 +01:00
|
|
|
|
this.$count.addClass( 'cgkit-floating-cart__count--bump' );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
$( document ).ready( function () {
|
|
|
|
|
|
FloatingCart.init();
|
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
|
|
} )( jQuery );
|