Articles on: Upsell widgets

Set Up Widget Callbacks

Overview



Widget callbacks refer to functions or blocks of JavaScript code executed automatically when certain events occur within a widget. These events could include loading the widget, clicking on elements within the widget, submitting forms, or any other user interactions.

How widget callbacks typically work



Each event, such as loading a widget, clicking a button, or submitting a form, triggers a specific callback action.
You write and insert JavaScript code that determines what happens when an event occurs.
Callbacks allow you to customize the widget's behavior to suit your needs. For example, you may want to track user actions or dynamically update the UI when certain actions are performed on the widget.
Event-based event callbacks start operating automatically in response to certain events triggered during the widget's lifecycle.
Callbacks are also used to track data. For example, you can record which buttons users click or which forms they submit in a widget, allowing you to gather analytics and insights.

How to apply widget callbacks




Upsell widgets




Open the editor of the upsell widget you would like apply widget callbacks for.
Under the general Settings tab, scroll down to Advanced field.
Apply Ready and Hide widget callbacks based on your preference.

Ready: This callback is triggered when a widget or component has finished loading and is ready to be interacted with by the user.

Hide: This callback is triggered when a widget or component is hidden or removed from the user interface.

Smart cart





Open the editor of the smart cart you would like apply widget callbacks for.
Under the general Settings tab, you can see the Advanced field.
Apply Ready and Hide widget callbacks based on your preference.

How widget callbacks can be applied in different scenarios



1. Mixed custom rules

Scenario: You have a shopping cart widget where you want to apply a special algorithm if certain conditions are met, like having two items tagged as "gift".

Cart ItemTags
AX tag
BX tag
CY tag


Widget Callback Usage: You would define a callback function that listens for changes in the shopping cart (e.g., items added or removed). Inside this callback, you'd check the tags of each item in the cart. If two items with the "gift" tag are detected, you trigger the "Frequently bought together" algorithm.

2. Customized styles by custom HTML:

Scenario: You want to rearrange the layout of elements within a widget, such as moving the variant option above the product’s image.
Widget Callback Usage: Use a callback function that runs after the widget has loaded. This function would customize the structure of the widget to reorder elements as required.

3. Customized data tracking:

Scenario: Integrate the widget's interactions with an external data tracking system like Google Analytics.
Widget Callback Usage: Implement callback functions that trigger when particular user actions occur within the widget (e.g., button clicks, form submissions).

For example, when a user clicks a "Buy Now" button within the widget, the callback function sends an event to Google Analytics to track the conversion.

Upsell widgets callbacks



Widget callbacks are functions that are executed automatically based on predefined events or actions within the upsell widget's lifecycle. For upsell widgets, typical callbacks might include:

`show: Displays the upsell widget to the user.

Callback Usage: You can attach a callback function to execute custom actions when the widget is displayed. This might include fetching updated product recommendations, adjusting UI elements, or tracking user interactions.

`hide`: Closes or hides the upsell widget.

Callback Usage: Define a callback function to handle actions when the widget is closed, such as saving user preferences, updating the main product view, or triggering follow-up actions.

`reload`: Updates the data displayed in the upsell widget.

Callback Usage: Use a callback function to manage actions when the widget’s data is reloaded, such as fetching fresh product recommendations or updating UI components to reflect changes.

`addToCart`: Triggered when a user adds an item to the cart from the upsell widget.

Callback Usage : You would define a callback function that handles the addition of items to the cart. This function might update the cart UI, trigger data tracking events, or perform other related actions.

`getProduct`: Used to fetch product information when a specific product handle is queried within the widget.

Callback Usage: You'd define a callback function that processes the retrieved product information, such as displaying details or updating related elements in the widget.

Event Listeners



Event listeners in upsell widgets allow you to respond to specific events triggered during the widget's lifecycle. For example:

`apz:widget:hide`: Triggered when the upsell widget is closed or hidden by the user.

Callback Usage: In the callback below the function changeCartDrawerWidgetStatus serves as a callback that manages the visibility and update data of a widget based on certain conditions related to the shopping cart.

const changeCartDrawerWidgetStatus = () => {
	// Get smart cart product amount
	const subtotal = Number(window.AfterShipPersonalization?.Cart?.subtotal)
	// Determine whether to display a widget 
	const isShowOffer = subtotal < 50 && !window.AfterShipPersonalization?.Cart?.data?.items?.some((item)=>item?.properties?.['_apz_offer_id'] === widget?.id)
	// Determine if the widget is hidden 
	const isHideOffer = subtotal > 50 || window.AfterShipPersonalization?.Cart?.data?.items?.some((item)=>item?.properties?.['_apz_offer_id'] === widget?.id)

	if(isShowOffer) {
		// Call the show method to display the widget. 
		widget?.show?.()
	}
	if(isHideOffer) {
		// Call the hide method to hide the widget.
		widget?.hide?.()
	}
	if(subtotal>50){
		// Determine if cart line data needs to be deleted
		window.AfterShipPersonalization?.Cart?.data?.items?.forEach((item)=>{
		if(item?.properties?.['_apz_offer_id'] === widget?.id){
			// Call the remove method to delete cart line data
			window.AfterShipPersonalization?.Cart?.remove?.(item?.key)
		}
		})
	}
	// Delete cart line data if conditions are met
	if(Number(subtotal)<=0){
			window.AfterShipPersonalization?.Cart?.data?.items?.forEach((item)=>{
				window.AfterShipPersonalization?.Cart?.remove?.(item?.key)
			})
		}
	   
   }
   changeCartDrawerWidgetStatus();
   document.addEventListener('apz:smartCart:update', changeCartDrawerWidgetStatus)


In the context of a "Smart Cart" widget, which likely serves as an advanced or enhanced version of a typical shopping cart interface, widget callbacks and event listeners are essential for managing its lifecycle, updating data dynamically, and responding to user interactions. Here's how these concepts relate specifically to the Smart Cart widget:

Smart cart widget callbacks



Smart Cart Widget callbacks allow developers to define custom behaviors that are triggered automatically during specific events or actions within the widget's lifecycle. Key functions typically provided include:

`show`: Displays the Smart Cart widget to the user.

Callback Usage: You can attach a callback to execute custom actions when the Smart Cart is shown, such as updating its content based on the latest data or adjusting the UI elements.

`hide`: Closes or hides the Smart Cart widget.

Callback Usage: Define a callback to handle actions like saving the cart state, updating related UI elements, or triggering cleanup tasks when the Smart Cart is closed.

`reload`: Updates the data and UI of the Smart Cart widget.

Callback Usage: Use a callback to fetch updated cart data and refresh the widget's display accordingly.

Event Listeners



Event listeners allow you to respond to specific events triggered during the Smart Cart widget's lifecycle. These events provide hooks into critical points where you may want to perform actions based on user interactions or changes in data:

`apz:smartCart:update`: Triggered when the Smart Cart's data is modified, such as when items are added, removed, or their quantities are adjusted.
`apz:smartCart:hide`: Fired when the Smart Cart is closed or hidden by the user.

const changeSmartCartStatus = () => {
	// Get smart cart product amount
	const subtotal = Number(window.AfterShipPersonalization?.Cart?.subtotal)
	// Determine if smart cart is displayed
	const isShowSmartCart = subtotal >100 && window.AfterShipPersonalization?.Cart?.data?.items?.length > 2	
    // If the condition is met, call the show method to display the smart cart.
	if(isShowSmartCart){
		widget?.show?.()
	}
	// Delete cart line data if conditions are met
	if(Number(subtotal)<=0){
			window.AfterShipPersonalization?.Cart?.data?.items?.forEach((item)=>{
				window.AfterShipPersonalization?.Cart?.remove?.(item?.key)
			})
		}
	   
   }
   changeSmartCartStatus();
   document.addEventListener('apz:smartCart:hide', changeSmartCartStatus)


Use cases



1. Customized styles

For example, move the variant option above the product’s image.



const handleVariants = ()=>{
		const element = document.querySelector('[data-personalization-id="00163"]');
		if(element) {
			const widgetRecButtonWrappers = element.querySelectorAll('.widget_rec_button_wrapper');
			widgetRecButtonWrappers.forEach(wrapper => {
				wrapper.style.display = 'none';
				wrapper.style.visibility = 'hidden';
			});
			const amRecItemContainers = element.querySelectorAll('.am_rec_item-container');
			
			amRecItemContainers?.forEach((container,index) => {
				container.style.position = 'relative';
				const actionDiv = document.createElement('div');
				actionDiv.className = 'am_rec_item_action';
				actionDiv.style.position = 'absolute';
				actionDiv.style.bottom = '12px';
				actionDiv.style.width = '100%';
				actionDiv.style.backgroundColor = '#cccccc4d';
				actionDiv.style.display = 'none';
				container.appendChild(actionDiv);
			
				const initButton = document.createElement('button');
				initButton.className = 'btn_icon';
				initButton.style.width = '100%';
				initButton.style.height = '40px';
				initButton.style.cursor = 'pointer';
				initButton.style.border = 'none';
				initButton.style.backgroundColor = 'transparent';
			
				const svgIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
				svgIcon.setAttribute('width', '20');
				svgIcon.setAttribute('height', '20');
				svgIcon.setAttribute('viewBox', '0 0 24 24');
				svgIcon.setAttribute('fill', 'none');
				svgIcon.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
				svgIcon.classList.add('icon', 'icon-shopping-bag');
			
				const path1 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
				path1.setAttribute('fill-rule', 'evenodd');
				path1.setAttribute('clip-rule', 'evenodd');
				path1.setAttribute('d', 'M16 20.6644H8C5.239 20.6644 3 18.4652 3 15.7534V8.51857C3 7.9764 3.448 7.53638 4 7.53638H20C20.552 7.53638 21 7.9764 21 8.51857V15.7534C21 18.4652 18.761 20.6644 16 20.6644Z');
				path1.setAttribute('stroke', '#111111');
				path1.setAttribute('stroke-linecap', 'round');
				path1.setAttribute('stroke-linejoin', 'round');
				path1.setAttribute('vector-effect', 'non-scaling-stroke');
			
				const path2 = document.createElementNS('http://www.w3.org/2000/svg', 'path');
				path2.setAttribute('d', 'M16 10.8424V4.94925C16 3.86393 15.105 2.98486 14 2.98486H10C8.895 2.98486 8 3.86393 8 4.94925V10.8424');
				path2.setAttribute('stroke', '#111111');
				path2.setAttribute('stroke-linecap', 'round');
				path2.setAttribute('stroke-linejoin', 'round');
				path2.setAttribute('vector-effect', 'non-scaling-stroke');
			
				svgIcon.appendChild(path1);
				svgIcon.appendChild(path2);
				initButton.appendChild(svgIcon);
				actionDiv.appendChild(initButton);




				container.addEventListener('mouseenter', function() {
					actionDiv.style.display = 'block';
				});
			
				container.addEventListener('mouseleave', function() {
					const isCreatedVariantDiv = container.querySelector('.variant_div'); 
					if(!isCreatedVariantDiv){
						actionDiv.style.display = 'none';
					}
				});
				actionDiv.addEventListener('click',  (event) =>{ event.stopPropagation();})
				initButton.addEventListener('click',  async(event) =>{
					event.stopPropagation(); // Stop event bubbling
					amRecItemContainers?.forEach((item,item_index)=>{
						const actionDiv = item.querySelector('.am_rec_item_action'); 
						const variantDiv = item.querySelector('.variant_div'); 
						if (variantDiv && item_index!==index) {
							actionDiv.style.display = 'none';
							variantDiv.remove(); 
						}
					})

					const parentWithUpsellProductHandle = initButton.closest('[data-apz-upsell-product-handle]');
				if (parentWithUpsellProductHandle) {
						const upsellProductHandle = parentWithUpsellProductHandle.getAttribute('data-apz-upsell-product-handle');
						const product= await widget?.getProduct(upsellProductHandle)

						if(product?.variants?.length){
							const defaultVariant = product?.variants?.length === 1  && product?.variants?.[0]?.title === "Default Title"
							if(defaultVariant) {
								widget?.addToCart(product?.variants[0])()
								return;
							}
							const isCreatedVariantDiv = container.querySelector('.variant_div'); 
							if(!isCreatedVariantDiv){
								const variantDiv = document.createElement('div');
							variantDiv.className = 'variant_div';
							variantDiv.style.display = 'grid';
							variantDiv.style.gridTemplateColumns = 'repeat(2, 1fr)';
							variantDiv.style.gap = '1px';
							initButton.parentNode.insertBefore(variantDiv, initButton);
							product?.variants?.forEach((variant) => {
								const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
								svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
								svg.setAttribute("fill", "currentColor");
								svg.setAttribute("class", "bi bi-plus-lg");
								svg.setAttribute("viewBox", "0 0 512 512");
								svg.setAttribute('width', '20');
								svg.setAttribute('height', '20');
								svg.setAttribute('style', 'animation: rotation 2s infinite linear');
								// Create a path element
								const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
								path.setAttribute("fill-rule", "evenodd");
								path.setAttribute("d", "M304 48a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zm0 416a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zM48 304a48 48 0 1 0 0-96 48 48 0 1 0 0 96zm464-48a48 48 0 1 0 -96 0 48 48 0 1 0 96 0zM142.9 437A48 48 0 1 0 75 369.1 48 48 0 1 0 142.9 437zm0-294.2A48 48 0 1 0 75 75a48 48 0 1 0 67.9 67.9zM369.1 437A48 48 0 1 0 437 369.1 48 48 0 1 0 369.1 437z");
								// Add path element to SVG
								svg.appendChild(path);
								const style = document.createElement('style');
								style.type = 'text/css';
								style.innerHTML = `
									@keyframes rotation {
										from {
											transform: rotate(0deg);
										}
										to {
											transform: rotate(360deg);
										}
									}
								`;
								document.head.appendChild(style);
								const loadingIcon = document.createElement('div');
								loadingIcon.className = 'loading_svg';
								loadingIcon.appendChild(svg);					

								if (loadingIcon.classList.contains('loading_svg')) {
									loadingIcon.style.display = 'none';
									loadingIcon.style.color = '#fff';
									loadingIcon.style.alignItems = 'center';
									loadingIcon.style.justifyContent = 'center';
								}
								const btnVariant = document.createElement('button');
								const textSpan = document.createElement('span');
								textSpan.innerText = variant?.title;
								btnVariant.className = 'btn_variant';
								btnVariant.style.height = '40px';
								btnVariant.style.cursor = 'pointer';
								btnVariant.style.border = 'none';
								btnVariant.style.backgroundColor = '#000';
								btnVariant.style.color = '#fff';
								btnVariant.onclick = async () => {
									btnVariant.disabled = true;
									textSpan.innerText = ''; // Just clear the text
									loadingIcon.style.display = 'flex';
									await widget.addToCart(variant);
									btnVariant.disabled = false; 
									loadingIcon.style.display = 'none';
									textSpan.innerText = variant?.title;
								};
								variantDiv.appendChild(btnVariant);
								btnVariant.appendChild(textSpan);
								btnVariant.appendChild(loadingIcon);							
							})
							}
						}
				}
		
			});
			});
		}
	}
	handleVariants()



2. Custom rules

a. If the cart value is below $100, shoppers can choose 1 gift out of 5 gifts.

const changeCartDrawerWidgetStatus = () => {
	// Get smart cart product amount
	const subtotal = Number(window.AfterShipPersonalization?.Cart?.subtotal)
	// Determine whether to display a widget 
	const isShowOffer = subtotal > 0 && subtotal < 100 &&  !window.AfterShipPersonalization?.Cart?.data?.items?.some((item)=>item?.properties?.['_apz_offer_id'] === widget?.id)
	// Determine if the widget is hidden 
	const isHideOffer = subtotal <= 0  || subtotal > 100 || window.AfterShipPersonalization?.Cart?.data?.items?.some((item)=>item?.properties?.['_apz_offer_id'] === widget?.id)
	
	if(isShowOffer) {
		// Call the show method to display the widget. 
		widget?.show?.()
	}
	if(isHideOffer) {
		// Call the hide method to hide the widget.
		widget?.hide?.()
	}
	
	if(subtotal>100){
		const removeArr=[];
		// Determine if cart line data needs to be deleted
		window.AfterShipPersonalization?.Cart?.data?.items?.forEach((item)=>{
			if(item?.properties?.['_apz_offer_id'] === widget?.id){
				// Call the remove method to delete cart line data
				removeArr.push(item?.key)
			}
		})
		window.AfterShipPersonalization?.Cart?.remove?.(removeArr)
	}
	if(Number(subtotal)<=0){
		const removeArr=[];
			window.AfterShipPersonalization?.Cart?.data?.items?.forEach((item)=>{
				removeArr.push(item?.key)
			})
			window.AfterShipPersonalization?.Cart?.remove?.(removeArr)
		}
	   
   }
   changeCartDrawerWidgetStatus();
   document.addEventListener('apz:smartCart:update', changeCartDrawerWidgetStatus)


b. If the cart value is above $100, shoppers can choose 2 gifts out of 3 gifts.



const changeCartDrawerWidgetStatus = () => {
	const subtotal = Number(window.AfterShipPersonalization?.Cart?.subtotal)
	const isShowOffer= subtotal > 100 && Number(window.AfterShipPersonalization?.Cart?.data?.items?.filter((item)=>item?.properties?.['_apz_offer_id']=== widget?.id )?.length) < 2
	const isHideOffer= subtotal < 100 ||  Number(window.AfterShipPersonalization?.Cart?.data?.items?.filter((item)=>item?.properties?.['_apz_offer_id']=== widget?.id )?.length) === 2
	if(isShowOffer) {
		widget?.show?.()
	}
	if(isHideOffer) {
		widget?.hide?.()
	}
   if(subtotal<100){
	  const removeArr=[]
	  window.AfterShipPersonalization?.Cart?.data?.items?.forEach((item)=>{
	   if(item?.properties?.['_apz_offer_id']=== widget?.id){
		removeArr.push(item?.key)
	   }
	  })
	  window.AfterShipPersonalization?.Cart?.remove?.(removeArr)
	}
	if(Number(subtotal)<=0){
		const removeArr=[]
			window.AfterShipPersonalization?.Cart?.data?.items?.forEach((item)=>{
				removeArr.push(item?.key)
			})
			window.AfterShipPersonalization?.Cart?.remove?.(removeArr)
	}  
   }
   changeCartDrawerWidgetStatus();
   document.addEventListener('apz:smartCart:update', changeCartDrawerWidgetStatus)

Updated on: 28/06/2024

Was this article helpful?

Share your feedback

Cancel

Thank you!