Tracking add_to_cart with mini cart functionality

Hello there, I hope this is the right place to reach for support.

We are using Mergado Pack on our WooCommerce eshop (https://jagaia.cz/) to track ecommerce events with Google Analytics 4. Everything worked just fine until we modified our eshop to display the minicart popup after adding a product to the card both from the category and product detail.

The tracking continues to work fine when a product is added from a category listing but no tracking occurs when the product is added from the product detail page.

I nailed the problem down to the fact that the Mergado Pack is tracking the add_to_cart event by listening to a click to the “Add to cart” button. That I solved easily by adding the ajax_add_to_card CSS class (on our test site) to the button but that doesn’t work well since on the product detail page there is no element which contains the product data your tracking snippet requires.

Is there any way we could fix this problem except implementing the event ourselves and sending it with the mini-cart?

Thanks in advance!

Hello,

and thank you for contacting us!

Please try adding the following code to theme/%your_theme%/functions.php:

function custom_single_product_meta() {
    $mergado = new Mergado_Marketing_Pack();
    $mergado->productListData();
}

add_action('woocommerce_before_add_to_cart_form', 'custom_single_product_meta', 5);

This script should add the missing metadata to the product. Unfortunately, we cannot guarantee how this script will interact with product variants. Therefore, we recommend deploying it to your test shop first.

If the script above does not work, please do not hesitate to inform us.

Best regards
Mergado Pack team

Hello Robin,

thanks for your suggestion. This successfully sends the event but the data are incorrect exactly as you menioned. The selected variant is not respected. The div[data-metadata-product-list] doesn’t even contain data for all variants but just the first one apparently.

I guess we need to manage to get all variants data first to move forward. Would there be a way? If we manage that, I believe it would be possible to modify the script to get the selected variant.

Alternatively, would there be away to include the event script in the mini-cart that we are rendering? Honestly, it feels like a better solution in general since the click on the button doesn’t really guarantee the item is actually added to the cart. It is definitely possible to generate the script manually but I hope there are some tools I could use to make the job easier.

Thanks in advance!
Honza

Hello,

Loading all variants is problematic.

The second option, where it would be called only upon opening, might work.

You could possibly use one of these hooks:

woocommerce_before_mini_cart
woocommerce_before_mini_cart_contents
woocommerce_mini_cart_contents
woocommerce_widget_shopping_cart_total
woocommerce_widget_shopping_cart_before_buttons
woocommerce_widget_shopping_cart_after_buttons

And attach our non-AJAX add to cart action to them.

add_action( "woocommerce_mini_cart_contents", function() {
    // Trigger only on product detail
    if (is_product()) {
        // Trigger non ajax add to cart event
        (Mergado\Service\External\Google\GoogleAnalytics\GA4\Ga4ServiceIntegration::getInstance())->addToCart();
    }
}, 99 );

Without admin or FTP access at the moment, it’s a guess as to what might work for you. Please try the procedure as described above.

1 Like

Hello Robin,

that is actually very close to a solution that would work! I used the code you provided and I had to modify the AJAX request that we are sending, so it includes the product ID in “add-to-cart” field but once I did that, the event code is successsfully generated and rendered but there is a problem that the tracking code listens to the DOMContentLoaded event which was triggered way before the AJAX request even started.

I guess, I can just trigger the event manually within our mini-cart but that seems a bit too much of a workaround.

window.document.dispatchEvent(new Event("DOMContentLoaded", {
    bubbles: true,
    cancelable: true
}));

Thanks for your help!
Honza

1 Like

Hello Robin,

I have an update on this. I actually added the dispatchEvent snippet which seemed to work but didn’t realize one important fact. Triggering again the DOMContentLoaded event causes all the tracking snippets that listen to this event to run again. This triggers view_item again but also when you add multiple products to the cart without refreshing the page, the previous additions are triggered again too. You can see it e.g. when you visit Breathe in – JAGAIA and try to add one variant after another. The first variant will be tracked again once the second variant is added to the cart.

It actually makes sense because the listeners are still registered. I can see two solutions:

  1. Ability to output the tracking script as part of the mini cart without the listener to the DOMContentLoaded event.
  2. Modify the listener, so it deregisters itself upon running.

Thanks for the support!
Honza

The addToCart function from Ga4ServiceIntegration is currently returning only a string containing the addToCart code.

For now, you can try using replace or preg_replace on the DOMContentLoaded part to insert your custom event name for that function before echoing it to the page in the custom action within functions.php.

Hello Robin,

thanks, that didn’t cross my mind and I now have a working solution!

So, what I did is that using str_replace I modify the event listener to a custom event, so the tracking script eventually looks like this:

<script>
  document
    .querySelector("#cart-modal .widget_shopping_cart_content")
    .addEventListener(
      "mergado_add_to_cart", 
      function () {
        gtag('event', 'add_to_cart', <?php echo json_encode($eventObject) ?>);
      });
</script>

And in my custom code, upon displaying the mini cart, I am triggering the custom event:

document.querySelector("#cart-modal .widget_shopping_cart_content").dispatchEvent(
  new CustomEvent("mergado_add_to_cart")
);

Thanks to the fact that the events are triggered on #cart-modal .widget_shopping_cart_content element which is replaced once the AJAX request is finished, the event listener is reset every time the mini cart appears and consequentialy the tracking only occurs once.

Still, to me it would seem more appropriate to modify your addToCart code, so it doesn’t listen to DOMContentLoaded but just runs once inserted into DOM. Maybe the addToCart method could have a parameter that would control whether to output the tracking snippet with the event listener or not.

Anyways, thanks a lot for your support and guiding me towards the solution!
Have a good one!
Honza

Hello @robin.fuksa!

I have yet another update. We just noticed that adding the add-to-cart parameter to the AJAX request causes WooCommerce to add every product to the shopping cart twice! The reason for this is that when add-to-cart is present, the regular form handler (WC_Form_Handler->addToCart()) adds the product to the cart as well as the AJAX handler (WC_AJAX->addToCart()) does.

I was only able to come up with a quick dirty hack below which works for now but I would rather prefer if the plugin’s code was updated to support both $_POST['add-to-cart'] which should be used for form actions and $_POST['product_id'] which should be used for AJAX actions.

// This is a dirty hack to make the ajax add to cart work with the Mergado tracking script
// Their code only checks for the 'add-to-cart' parameter, but WooCommerce AJAX add to cart uses 'product_id'
// When 'add-to-cart' is present from the beginning (sent by the AJAX request in scripts.js), the regular
// add to cart handler is called and that doesn't return the snippets. When both are present,
// product is added to the cart twice.
add_action("woocommerce_ajax_added_to_cart", function() {
	if(!is_ajax()) return;

	$_POST["add-to-cart"] = $_POST["product_id"];
});

Is that anything you could take into consideration?

Thanks!
Honza

Oh my, there was yet another problem. This is getting more complicated than I wished for :smiley:

You originally proposed to use the is_product() conditional tag to only output the tracking snippet when called from a single product page but that didn’t work within the AJAX request, so I changed it to is_singular() withoun too much thinking.

It turns out this didn’t work well either. Eventually I ended up adding a custom parameter single_product to the AJAX request and then checking for it in the action.

Once we wrap this up, I will post a complete code for others to take advantage of.

1 Like

Hello,

On DOMContentLoaded (meaning it runs only after the page is fully loaded), it is executed because the main code needs to be loaded. This is something we truly cannot change.

Regarding your quick fix, our GA4 code (which only triggers events to GA4 and is run by WooCommerce) has nothing to do with adding a product to the cart twice. If we are not mistaken, when it sends an AJAX request with the add-to-cart label, it triggers a WooCommerce function, which in turn triggers our function.

The AddToCart function in the Mergado pack is used for form requests, and for the purposes of this case, we just adjusted it slightly. AJAX is handled differently on the frontend in the addToCartAjax function.

To wrap it all up, is there any plugin that would help you with this issue? We could integrate this plugin into the Mergado PACK to make it fully compatible with your needs regarding adding an addToCart event for GA4.

Thank you.

Best regards
Mergado PACK team

Hi Robin,

I understand that DOMContentLoaded is necessary. What I am proposing is an argument for the addToCart function, so the event code can be outputed without the event listener if necessary (which would be solution for my case). Your regular tracking wouldn’t set this argument, so everything would work normally.

As for the quick fix, since your tracking logic is originally intended for the form handler, it is of course expecting the $_POST[add-to-cart] variable, which is fine and indeed not causing the problem with products being added twice. The trouble here, is that ajax handler uses $_POST['product_id']instead, so the AJAX request doesn’t have the necessary data for your tracking logic and thus no tracking code is produced even when I call the addToCart manually. To fix this, I had to send the product ID in the $_POST['add-to-cart']variable. Then your functionality has all the info and produces tracking script. But doing so, causes WooCommerce to trigger both AJAX and form handlers, resulting in products being added to cart twice.

So what I thought you could do, is to modify the addToCart function, so it tries to use $_POST['product_id']in case $_POST['add-to-cart'] is not set. In my opinion, that shouldn’t cause any problem anywhere else.

I completely understand if you are not willing to make any customizations since our implementation is custom, it just doesn’t seem like a heavy modification to me, so I dare to ask.

Have a good one!
Honza

1 Like

Hello,

Thank you for your proposal. Since this is a quick custom fix, I’ve sent you an edited version of Mergado PACK via email.

Please try this quick fix and see if it better meets your needs. Afterward, we can share the outcome here.

Best regards
Mergado PACK team