1/** 2 * Event Enum for Standard GA4 Events 3 * Reference: https://support.google.com/analytics/answer/9267735#online-sales 4 */ 5const GA4Event = Object.freeze({ 6 VIEW_ITEM: "view_item", 7 VIEW_ITEM_LIST: "view_item_list", 8 ADD_TO_CART: "add_to_cart", 9 SELECT_CONTENT: "select_content", 10 SELECT_ITEM: "select_item", 11 // Add more events as needed 12}); 13 14/** 15 * Custom Event Types for Foodsocial 16 */ 17const FoodsocialEventType = Object.freeze({ 18 BANNER_AD_CLICK: "banner_ad_click", 19 BANNER_AD_VIEW: "banner_ad_view", 20 RECIPE_INGREDIENT_CLICK: "recipe_ingredient_click", 21 SHOPPING_CART_ICON_CLICK: "shopping_cart_icon_click", 22 CAROUSEL_INGREDIENT_CLICK: "carousel_ingredient_ad_click", 23 CAROUSEL_INGREDIENT_VIEW: "carousel_ingredient_ad_view", 24 INSTACART_SHOPPING_LIST_CLICK: "instacart_shopping_list_click", 25 // TODO: BANNER VIEW ? in some other php file. 26}); 27 28/** 29 * Class representing a Foodsocial Event 30 */ 31class FoodsocialEvent { 32 /** 33 * @param {string} eventType - The type of event (FoodsocialEventType). 34 * @param {boolean} isShopify - Whether the event is related to Shopify. 35 * @param {Array} relatedShopifyIds - A list of the shopify ids related to this event. 36 * @param {Object} item - The main object to pass to GA4's `gtag()` function. 37 */ 38 constructor(eventType, isShopify, relatedShopifyIds = [], item) { 39 if (typeof eventType !== 'string' || !Object.values(FoodsocialEventType).includes(eventType)) { 40 throw new Error(`Invalid eventType: ${eventType}`); 41 } 42 43 this.eventType = eventType; 44 this.isShopify = Boolean(isShopify); 45 this.relatedShopifyIds = Array.isArray(relatedShopifyIds) ? relatedShopifyIds.filter(id => id) : []; 46 this.item = item || {}; 47 } 48 49/** 50 * Sends the event data to GA4 (or logs the input to the console for testing) 51 */ 52async sendToGA4() { 53 if (typeof gtag === "undefined") { 54 console.error("GA4 gtag() function is not defined."); 55 return; 56 } 57 58 /* Comment for testing */ 59 // // Mock gtag calls by printing to the console 60 // const mockGtag = (event, eventName, eventParams) => { 61 // console.log(`gtag("${event}", "${eventName}",`, eventParams, `);`); 62 // }; 63 64 let item; 65 switch (this.eventType) { 66 case FoodsocialEventType.BANNER_AD_CLICK: 67 case FoodsocialEventType.BANNER_AD_VIEW: 68 gtag("event", GA4Event.SELECT_CONTENT, { 69 content_type: "banner_ad", 70 content_id: this.item.content_id.split('.')[0], /* get the name without the '.jpg' at the end of the adname attr */ 71 }); 72 break; 73 74 case FoodsocialEventType.RECIPE_INGREDIENT_CLICK: 75 gtag("event", GA4Event.SELECT_CONTENT, { 76 content_type: "recipe_ingredient", 77 content_id: this.item.content_id, /* ingredient_id attr should be passed here */ 78 }); 79 break; 80 81 case FoodsocialEventType.SHOPPING_CART_ICON_CLICK: 82 case FoodsocialEventType.CAROUSEL_INGREDIENT_CLICK: 83 if(!this.isShopify || !this.relatedShopifyIds){ /* only track click on shopify productions on the shopping cart for now */ 84 /* maybe consider amazon products in the future */ 85 console.warn(`Unhandled event type: ${this.eventType} when isShopify: ${this.isShopify}`); 86 return; 87 } 88 item = await getItem(this.relatedShopifyIds[0]); 89 gtag("event", GA4Event.VIEW_ITEM, item); 90 break; 91 92 case FoodsocialEventType.CAROUSEL_INGREDIENT_VIEW: 93 if(!this.isShopify || !this.relatedShopifyIds){ /* carousels only allow shopify products */ 94 console.warn(`Unhandled event type: ${this.eventType} when isShopify: ${this.isShopify}`); 95 return; 96 } 97 item = await getItem(this.relatedShopifyIds[0]); 98 const items = item?.items || []; 99 const slug = this.item.slug; 100 const name = this.item.name; 101 102 let dict = { 103 items, // Spread doesn't work directly on arrays; just assign it 104 item_list_id: slug, 105 item_list_name: name 106 }; 107 108 gtag("event", GA4Event.VIEW_ITEM_LIST, dict); 109 break; 110 case FoodsocialEventType.INSTACART_SHOPPING_LIST_CLICK: 111 let creatorId = this.item.creator_id 112 let recipeId = this.item.recipe_id; 113 if(!creatorId || !recipeId){ 114 console.warn('Unable to track event clicks on instacart link') 115 } 116 gtag('event', FoodsocialEventType.INSTACART_SHOPPING_LIST_CLICK, this.item); 117 break 118 default: 119 console.warn(`Unhandled event type: ${this.eventType}`); 120 } 121} 122} 123 124function getItem(shopifyId) { 125 const encodedId = btoa(`gid://shopify/Product/${shopifyId}`); 126 127 return client.product.fetch(encodedId) 128 .then((product) => { 129 if (!product) { 130 console.error("No product found with the given ID (" + shopifyId + ")"); 131 return null; 132 } 133 const variant = product.variants[0]; 134 const price = parseFloat(variant.price.amount); 135 const quantity = 1; 136 const productDetails = { 137 currency: variant.price.currencyCode, 138 value: (price * quantity).toFixed(2), 139 items: [ 140 { 141 item_id: `shopify_US_${shopifyId}_${variant.sku}`, 142 affiliation: paleo_GetCookie("firstAuthor") || "None", 143 item_name: product.title, 144 item_brand: product.vendor || "Unknown Brand", 145 item_category: product.productType || "Uncategorized", 146 item_variant: variant.title || "Default Variant", 147 price: price.toFixed(2), 148 quantity: quantity, 149 } 150 ] 151 }; 152 153 return productDetails; 154 }) 155 .catch((error) => { 156 console.error("Error fetching product details ("+shopifyId+")", error); 157 return null; 158 }); 159} 160 161function custom_gtag(event, name, val, add_key, add_val, add_key2, add_val2){ 162 if(add_key2 !== undefined && add_key2 != ''){ 163 gtag('event', event, {[name]: val, [add_key]: add_val, [add_key2]: add_val2}); 164 } 165 else if(add_key !== undefined && add_key != ''){ 166 gtag('event', event, {[name]: val, [add_key]: add_val}); 167 } 168 else{ 169 gtag('event', event, {[name]: val}); 170 } 171} 172 173window.dataLayer = window.dataLayer || []; 174function gtag(){ dataLayer.push(arguments); } 175gtag('js', new Date()); 176 177(function(w,d,s,l,i){ 178 w[l] = w[l] || []; 179 w[l].push({'gtm.start': new Date().getTime(), 180 event: 'gtm.js'}); 181 var f=d.getElementsByTagName(s)[0], 182 j=d.createElement(s), 183 dl=l!='dataLayer'?'&l='+l:''; 184 j.async=true;
185 j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl; 186 f.parentNode.insertBefore(j,f); 187})(window,document, 'script', 'dataLayer', customLinks.googleTagManager);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.