5. Listening to store events
Some services provided by third-party solutions may rely on store events. Events are notifications a web application automatically broadcasts whenever a user performs an important action in a store, such as adding items to the shopping cart or accessing a product page.
In these scenarios, your Pixel app must be able to listen to the desired events and provide the required data to the third-party service in question.
Instructions
- Open the
react/index.tsx
file. - Write your script based on your Pixel app needs. Some store events you can use in your Pixel app are:
addToCart
- Triggered when a product is added to the cart.removeFromCart
- Triggered when a product is removed from the cart.pageView
- Triggered on every loaded page view.productImpression
- Triggered when product information is visible on the page currently being accessed by users.
All the available event properties are written in TypeScript. For a comprehensive list of properties, please refer to this file.
This is an example of an event implementation in the Google Tag Manager Pixel app:
_315import { canUseDOM } from 'vtex.render-runtime'_315_315import push from './modules/push'_315import {_315 Order,_315 PixelMessage,_315 ProductOrder,_315 Impression,_315 CartItem,_315} from './typings/events'_315import { AnalyticsEcommerceProduct } from './typings/gtm'_315_315export default function() {_315 return null_315} // no-op for extension point_315_315export function handleEvents(e: PixelMessage) {_315 switch (e.data.eventName) {_315 case 'vtex:pageView': {_315 push({_315 event: 'pageView',_315 location: e.data.pageUrl,_315 page: e.data.pageUrl.replace(e.origin, ''),_315 referrer: e.data.referrer,_315 ...(e.data.pageTitle && {_315 title: e.data.pageTitle,_315 }),_315 })_315_315 return_315 }_315_315 case 'vtex:productView': {_315 const { selectedSku, productName, brand, categories } = e.data.product_315_315 let price_315_315 try {_315 price = e.data.product.items[0].sellers[0].commertialOffer.Price_315 } catch {_315 price = undefined_315 }_315_315 const data = {_315 ecommerce: {_315 detail: {_315 products: [_315 {_315 brand,_315 category: getCategory(categories),_315 id: selectedSku.itemId,_315 name: productName,_315 variant: selectedSku.name,_315 price,_315 },_315 ],_315 },_315 },_315 event: 'productDetail',_315 }_315_315 push(data)_315_315 return_315 }_315_315 case 'vtex:productClick': {_315 const { productName, brand, categories, sku } = e.data.product_315_315 let price_315_315 try {_315 price = e.data.product.items[0].sellers[0].commertialOffer.Price_315 } catch {_315 price = undefined_315 }_315_315 const data = {_315 event: 'productClick',_315 ecommerce: {_315 click: {_315 products: [_315 {_315 brand,_315 category: getCategory(categories),_315 id: sku.itemId,_315 name: productName,_315 variant: sku.name,_315 price,_315 },_315 ],_315 },_315 },_315 }_315_315 push(data)_315_315 return_315 }_315_315 case 'vtex:addToCart': {_315 const { items } = e.data_315_315 push({_315 ecommerce: {_315 add: {_315 products: items.map((sku: any) => ({_315 brand: sku.brand,_315 category: sku.category,_315 id: sku.skuId,_315 name: sku.name,_315 price: `$\{sku.price\}`,_315 quantity: sku.quantity,_315 variant: sku.variant,_315 })),_315 },_315 currencyCode: e.data.currency,_315 },_315 event: 'addToCart',_315 })_315_315 return_315 }_315_315 case 'vtex:removeFromCart': {_315 const { items } = e.data_315_315 push({_315 ecommerce: {_315 currencyCode: e.data.currency,_315 remove: {_315 products: items.map((sku: any) => ({_315 brand: sku.brand,_315 id: sku.skuId,_315 category: sku.category,_315 name: sku.name,_315 price: `$\{sku.price\}`,_315 quantity: sku.quantity,_315 variant: sku.variant,_315 })),_315 },_315 },_315 event: 'removeFromCart',_315 })_315_315 return_315 }_315_315 case 'vtex:orderPlaced': {_315 const order = e.data_315_315 const ecommerce = {_315 purchase: {_315 actionField: getPurchaseObjectData(order),_315 products: order.transactionProducts.map((product: ProductOrder) =>_315 getProductObjectData(product)_315 ),_315 },_315 }_315_315 push({_315 // @ts-ignore_315 event: 'orderPlaced',_315 ...order,_315 ecommerce,_315 })_315_315 // Backwards compatible event_315 push({_315 ecommerce,_315 event: 'pageLoaded',_315 })_315_315 return_315 }_315_315 case 'vtex:productImpression': {_315 const { currency, list, impressions, product, position } = e.data_315 let oldImpresionFormat: Record<string, any> | null = null_315_315 if (product != null && position != null) {_315 // make it backwards compatible_315 oldImpresionFormat = [_315 getProductImpressionObjectData(list)({_315 product,_315 position,_315 }),_315 ]_315 }_315_315 const parsedImpressions = (impressions || []).map(_315 getProductImpressionObjectData(list)_315 )_315_315 push({_315 event: 'productImpression',_315 ecommerce: {_315 currencyCode: currency,_315 impressions: oldImpresionFormat || parsedImpressions,_315 },_315 })_315_315 return_315 }_315_315 case 'vtex:userData': {_315 const { data } = e_315_315 if (!data.isAuthenticated) {_315 return_315 }_315_315 push({_315 event: 'userData',_315 userId: data.id,_315 })_315_315 return_315 }_315_315 case 'vtex:cartLoaded': {_315 const { orderForm } = e.data_315_315 push({_315 event: 'checkout',_315 ecommerce: {_315 checkout: {_315 actionField: {_315 step: 1,_315 },_315 products: orderForm.items.map(getCheckoutProductObjectData),_315 },_315 },_315 })_315_315 break_315 }_315_315 default: {_315 break_315 }_315 }_315}_315_315function getPurchaseObjectData(order: Order) {_315 return {_315 affiliation: order.transactionAffiliation,_315 coupon: order.coupon ? order.coupon : null,_315 id: order.orderGroup,_315 revenue: order.transactionTotal,_315 shipping: order.transactionShipping,_315 tax: order.transactionTax,_315 }_315}_315_315function getProductObjectData(product: ProductOrder) {_315 return {_315 brand: product.brand,_315 category: product.categoryTree?.join('/'),_315 id: product.sku,_315 name: product.name,_315 price: product.price,_315 quantity: product.quantity,_315 variant: product.skuName,_315 }_315}_315_315function getCategory(rawCategories: string[]) {_315 if (!rawCategories || !rawCategories.length) {_315 return_315 }_315_315 return removeStartAndEndSlash(rawCategories[0])_315}_315_315// Transform this: "/Apparel & Accessories/Clothing/Tops/"_315// To this: "Apparel & Accessories/Clothing/Tops"_315function removeStartAndEndSlash(category?: string) {_315 return category?.replace(/^\/|\/$/g, '')_315}_315_315function getProductImpressionObjectData(list: string) {_315 return ({ product, position }: Impression) => ({_315 brand: product.brand,_315 category: getCategory(product.categories),_315 id: product.sku.itemId,_315 list,_315 name: product.productName,_315 position,_315 price: `$\{product.sku.seller!.commertialOffer.Price\}`,_315 variant: product.sku.name,_315 })_315}_315_315function getCheckoutProductObjectData(_315 item: CartItem_315): AnalyticsEcommerceProduct {_315 return {_315 id: item.id,_315 name: item.name,_315 category: Object.keys(item.productCategories ?? {}).reduce(_315 (categories, category) =>_315 categories ? `$\{categories\}/$\{category\}` : category,_315 ''_315 ),_315 brand: item.additionalInfo?.brandName ?? '',_315 variant: item.skuName,_315 price: item.sellingPrice / 100,_315 quantity: item.quantity,_315 }_315}_315_315if (canUseDOM) {_315 window.addEventListener('message', handleEvents)_315}