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.tsxfile.
- 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}