// @vitest-environment happy-dom
import Listener from "./listener.js";
import Cart from "../cart.js";
import { createCartItem } from "../cart-item.js";
/**
* 'ga4_add_to_cart' - triggered when the user clicks a '+' button on an item in the sidecart.
* Uses the CartJS cart to query the item that was updated and generate the event payload.
* @class
* @extends Listener
* @param {Object} cart - the CartJS cart object
*/
export class AddToCartButtonListener extends Listener {
eventName = "ga4_add_to_cart";
constructor(cart) {
super();
this.cart = new Cart(cart);
}
/**
* Getter that returns an array of items that were added to the cart.
* The array is empty if no items were added.
* Subclasses can override this method to control the event payload.
* @memberof AddToCartButtonListener
* @type {Array}
*/
get addedItems() {
if (!this.link) return [];
const newQuantity = parseInt(this.link.getAttribute('data-cart-quantity'), 10);
const id = this.link.getAttribute('data-cart-update-id');
const oldItem = this.cart.getItemByVariantID(id);
return (newQuantity > oldItem.quantity)
? [{ ...oldItem, quantity: newQuantity }]
: [];
}
listen() {
const sidecart = document.getElementById('sidecart');
sidecart.addEventListener('click', (({ target }) => {
this.link = target.closest('a[data-cart-update-id]');
this.triggerIfAdded();
}));
}
triggerIfAdded() {
if (this.addedItems.length > 0) {
this.trigger(this.addedItems);
}
}
createPayload(addedItems) {
const currency = this.cart.currency;
const items = [];
let value = 0;
for (const item of addedItems) {
const quantity = Math.abs(this.getQuantityDifference(item));
const cartItem = createCartItem(item, { quantity, currency });
items.push(cartItem);
value += Math.abs(cartItem.price * cartItem.quantity);
}
return {
ecommerce: {
value,
currency,
items
}
};
}
/**
* Compares the added item against the existing cart and returns the difference in quantity.
* @memberof AddToCartButtonListener
* @param {Object} item - the item added
* @param {string | number} item.id
* @param {number} item.quantity
* @returns {number} the quantity increase in the added item
*/
getQuantityDifference(item) {
const oldItem = this.cart.getItem(item);
const oldQuantity = oldItem ? oldItem.quantity : 0;
return item.quantity - oldQuantity;
}
}
/**
* 'ga4_add_to_cart' - triggered when the quantity of an item is increased on the /cart page.
* Listens for the cart form submit event.
* Uses the CartJS cart to query the item that was updated and generate the event payload.
* Reuses the `createPayload` implementation of {@link AddToCartButtonListener}.
* @class
* @extends AddToCartButtonListener
* @param {Object} cart - the CartJS cart object
*/
export class AddToCartFormListener extends AddToCartButtonListener {
constructor(cart) {
super(cart);
this.form = document.querySelector('form.cart');
}
get addedItems() {
const addedItems = [];
const newQuantities = this.getNewQuantities();
newQuantities.forEach((quantity, i) => {
const oldItem = this.cart.items[i];
const newItem = { ...oldItem, quantity };
if (quantity > oldItem.quantity) addedItems.push(newItem);
});
return addedItems;
}
listen() {
this.form.addEventListener('submit', () => this.triggerIfAdded());
}
/**
* Gets the new quantity values for each item on the /cart page.
* This is used to determine which items increased in quantity (ie, addedItems)
* @memberof AddToCartFormListener
* @returns {Array<number>} the quantity values from the cart form
*/
getNewQuantities() {
const formData = new FormData(this.form);
const updates = formData.getAll("updates[]");
return updates.map((value) => parseInt(value, 10));
}
}
/**
* 'ga4_add_to_cart' - triggered when a request to '/cart/add.js' resolves successfully.
* Uses an XMLHttpRequest object to determine the item added to the cart.
* Uses the CartJS cart to query the item that was updated and generate the event payload.
* Reuses the `createPayload` implementation of {@link AddToCartButtonListener}.
* @class
* @extends AddToCartButtonListener
* @param {Object} request - the XMLHttpRequest object
* @param {Object} cart - the CartJS cart object
*/
export class AddToCartRequestListener extends AddToCartButtonListener {
constructor(request, cart) {
super(cart);
this.request = request;
}
get addedItems() {
const isAddRequest = this.request.responseURL.includes('/cart/add.js');
if (!isAddRequest) return [];
const responseBody = JSON.parse(this.request.responseText);
// response body may either be an item or an object containing an array of items.
return responseBody.items || [responseBody];
}
listen() {
this.request.addEventListener('load', () => this.triggerIfAdded());
}
}