1async function fetchMcpProductsData() { 2 try { 3 const response = await fetch(location.origin + '/backend/api/mcp/getNisProductsDataForMcp'); 4 5 if (!response.ok) { 6 throw new Error('API error from NIS products for MCP'); 7 } 8 9 const data = await response.json(); 10 return data; 11 } catch (error) { 12 console.error('Error: ', error); 13 } 14} 15 16/** 17 * Creation of an event listener to send events from the basket to MCP. 18 * 19 * When a push datalayer is sent, it is intercepted in order to retrieve these values and use them for MCP. 20 * These values are then sent to the new custom event if the event corresponds to add_to_cart or remove_from_cart. 21 */ 22 23window.dataLayer = window.dataLayer || new Proxy([], { 24 set: (obj, prop, value) => { 25 if (prop !== 'length' && value.event && (value.event === 'add_to_cart' || value.event === 'remove_from_cart')) { 26 const pushEvent = new CustomEvent('datalayerPushMcp', { 27 detail: value 28 }); 29 30 window.dispatchEvent(pushEvent); 31 } 32 33 return Reflect.set(obj, prop, value); 34 } 35}); 36 37window.addEventListener('datalayerPushMcp', async event => { 38 if (typeof SalesforceInteractions === 'undefined') { 39 return; 40 } 41 42 let detail = event.detail; 43 44 let eventName = (detail.event === 'add_to_cart' ? SalesforceInteractions.CartInteractionName.AddToCart : SalesforceInteractions.CartInteractionName.RemoveFromCart); 45 46 let datalayerItems = detail.ecommerce.items; 47 let lineItems = []; 48 49 datalayerItems.forEach((item) => { 50 let lineItem = { 51 catalogObjectType: "Product", 52 catalogObjectId: item.cp_ecom_product_name, 53 quantity: item.quantity, 54 price: item.price, 55 currency: "CHF" 56 } 57 lineItems.push(lineItem); 58 }); 59 60 // As Replace Cart can't be correctly handled in mcp we need to replace cart with an empty array and then make all add to cart calls like before. 61 // see : https://tcsgroup.atlassian.net/browse/WWW-7886 62 // See: https://developer.salesforce.com/docs/marketing/personalization/guide/event-options-salesforceinteractions.html#cartinteraction 63 64 65 if (eventName === SalesforceInteractions.CartInteractionName.AddToCart) { 66 // Tested that SalesforceInteractions.sendEvent returns a Promise 67 // So we await for remove from cart to answer before we do AddToCarts 68 await SalesforceInteractions.sendEvent({ 69 interaction: { 70 name: SalesforceInteractions.CartInteractionName.ReplaceCart, 71 lineItems: [] 72 }, 73 user: { 74 attributes: { 75 CheckoutConsent: false 76 } 77 } 78 }); 79 } 80 lineItems.forEach((lineItem) => { 81 SalesforceInteractions.sendEvent({ 82 interaction: { 83 name: eventName, 84 lineItem: lineItem 85 }, 86 user: { 87 attributes: { 88 CheckoutConsent: false 89 } 90 } 91 }); 92 }); 93 94 95}); 96 97function checkUsercentricsMcpStatus() { 98 if (typeof SalesforceInteractions === 'undefined') { 99 return; 100 } 101 let serviceId = "IQMIegSQF"; 102 if (!Usercentrics.isServiceIdAvailable(serviceId)) { 103 console.error(`Usercentrics Service ${serviceId} does not exist !`); 104 return; 105 } 106 107 var technologyName = Usercentrics.getServiceNameForId(serviceId); 108 109 if (Usercentrics.getConsentStatusForId(serviceId) === true) { 110 setMcpOptInConsent(); 111 } 112 window.addEventListener("ucEvent", function (e) { 113 if (e.detail[technologyName.toString()]) { 114 setMcpOptInConsent(); 115 } else { 116 setMcpOptOutConsent(); 117 } 118 }); 119} 120 121function setMcpOptInConsent() { 122 const consent = { 123 provider: "Consent Provider", 124 purpose: SalesforceInteractions.mcis.ConsentPurpose.Personalization, 125 status: SalesforceInteractions.ConsentStatus.OptIn 126 }; 127 SalesforceInteractions.updateConsents(consent); 128 SalesforceInteractions.reinit(); 129} 130 131function setMcpOptOutConsent() { 132 const consent = { 133 provider: "Consent Provider", 134 purpose: SalesforceInteractions.mcis.ConsentPurpose.Personalization, 135 status: SalesforceInteractions.ConsentStatus.OptOut 136 }; 137 SalesforceInteractions.updateConsents(consent); 138} 139 140function pushDataLayerFromMcp(context) { 141 if ( 142 context.gaProductName != '' 143 || context.gaProductName1 != '' 144 || context.gaProductName2 != ''
145 || context.items.length > 0 146 ) { 147 fetchMcpProductsData().then(data => { 148 let relatedArea = SalesforceInteractions.getSitemapConfig().global.contentZones.filter((zone) => zone.name === context.contentZone); 149 if (relatedArea.length > 0) { 150 let areaSelector; 151 if (relatedArea[0].selector) { 152 areaSelector = SalesforceInteractions.getSitemapConfig().global.contentZones.filter((zone) => zone.name === context.contentZone)[0].selector; 153 } else { 154 areaSelector = '#' + SalesforceInteractions.getSitemapConfig().global.contentZones.filter((zone) => zone.name === context.contentZone)[0].name; 155 areaSelector = areaSelector.replaceAll('_', '-'); 156 } 157 158 let productData = []; 159 if (context.imgSrc1 && context.imgSrc2 && context.secondItemRendering) { 160 const linkSelectors = document.querySelector(areaSelector).querySelectorAll('.evg-btn'); 161 // Search page product offers with 2 products 162 // Product 1 has productName defined 163 if (context.gaProductName1 !== 'NOT_GA4_PRODUCT') { 164 const gaProduct1 = data[context.gaProductName1]; 165 const variant1 = (gaProduct1.coverType || 'N/A') 166 + '_' + (gaProduct1.familyVariant || 'N/A') 167 + '_' + (gaProduct1.breakdownVariant || 'N/A') 168 + '_N/A'; 169 const basicPrice1 = 170 context.normalPrice1 && context.normalPrice1 !== '' ? 171 Number(context.normalPrice1.replace(/[^0-9.-]+/g, "")) : 172 +gaProduct1.price; 173 let discount1 = 0; 174 if (context.discountPrice1 && context.discountPrice1 !== '') { 175 discount1 = ( 176 basicPrice1 - 177 Number(context.discountPrice1.replace(/[^0-9.-]+/g, "")) 178 ).toFixed(2); 179 } 180 let image1; 181 if (context.imgSrc1 && context.imgSrc1 !== '') { 182 image1 = context.imgSrc1.split('/').pop(); 183 } else if (context.videoId1 && context.videoId1 !== '') { 184 image1 = context.videoId1; 185 } 186 187 productData.push({ 188 image: image1, 189 campaignId: context.campaign, 190 campaignName: context.header, 191 productId: gaProduct1.id, 192 productShortName: gaProduct1.shortName, 193 category: gaProduct1.category, 194 variant: variant1, 195 price: basicPrice1.toFixed(2), 196 discount: discount1, 197 familyVariant: gaProduct1.familyVariant || "N/A", 198 breakdownVariant: gaProduct1.breakdownVariant || "N/A", 199 fullName: gaProduct1.fullName, 200 coverageAmount: gaProduct1.coverageAmount || "N/A", 201 link: linkSelectors[0] 202 }); 203 } 204 // Product 2 has productName defined 205 if (context.gaProductName2 !== 'NOT_GA4_PRODUCT') { 206 const gaProduct2 = data[context.gaProductName2]; 207 const variant2 = (gaProduct2.coverType || 'N/A') 208 + '_' + (gaProduct2.familyVariant || 'N/A') 209 + '_' + (gaProduct2.breakdownVariant || 'N/A') 210 + '_N/A'; 211 const basicPrice2 = 212 context.normalPrice2 && context.normalPrice2 !== '' ? 213 Number(context.normalPrice2.replace(/[^0-9.
213-]+/g, "")) : 214 +gaProduct2.price; 215 let discount2 = 0; 216 if (context.discountPrice2 && context.discountPrice2 !== '') { 217 discount2 = ( 218 basicPrice2 - 219 Number(context.discountPrice2.replace(/[^0-9.-]+/g, "")) 220 ).toFixed(2); 221 } 222 let image2; 223 if (context.imgSrc2 && context.imgSrc2 !== '') { 224 image2 = context.imgSrc2.split('/').pop(); 225 } else if (context.videoId2 && context.videoId2 !== '') { 226 image2 = context.videoId2; 227 } 228 229 productData.push({ 230 image: image2, 231 campaignId: context.campaign, 232 campaignName: context.header, 233 productId: gaProduct2.id, 234 productShortName: gaProduct2.shortName, 235 category: gaProduct2.category, 236 variant: variant2, 237 price: basicPrice2.toFixed(2), 238 discount: discount2, 239 familyVariant: gaProduct2.familyVariant || "N/A", 240 breakdownVariant: gaProduct2.breakdownVariant || "N/A", 241 fullName: gaProduct2.fullName, 242 coverageAmount: gaProduct2.coverageAmount || "N/A", 243 link: linkSelectors[1] 244 }); 245 } 246 247 } else if ( 248 context.items && context.items.length > 0 249 ) { 250 // Carousel object is rendered on the page 251 context.items.forEach((item, index) => { 252 if (item.gaProductName && item.gaProductName !== 'NOT_GA4_PRODUCT') { 253 const gaProductName = item.gaProductName; 254 const gaProduct = data[gaProductName]; 255 const linkSelector = document.querySelector(areaSelector).querySelectorAll('.evg-btn')[index]; 256 257 const basicPrice = 258 item.price && item.price !== '' ? 259 Number(item.price.replace(/[^0-9.-]+/g, "")) : 260 +gaProduct.price; 261 262 let discount = 0; 263 if (item.discountPrice && item.discountPrice !== '') { 264 discount = ( 265 basicPrice - 266 Number(item.discountPrice.replace(/[^0-9.-]+/g, "")) 267 ).toFixed(2); 268 } 269 270 const variant = (gaProduct.coverType || 'N/A') 271 + '_' + (gaProduct.familyVariant || 'N/A') 272 + '_' + (gaProduct.breakdownVariant || 'N/A') 273 + '_N/A'; 274 275 productData.push({ 276 image: item.imgSrc.split('/').pop(), 277 campaignId: context.campaign, 278 campaignName: item.title, 279 productId: gaProduct.id, 280 productShortName: gaProduct.shortName, 281 category: gaProduct.category, 282 variant: variant, 283 price: basicPrice.toFixed(2), 284 discount: discount, 285 familyVariant: gaProduct.familyVariant || "N/A", 286 breakdownVariant: gaProduct.breakdownVariant || "N/A", 287 fullName: gaProduct.fullName, 288 coverageAmount: gaProduct.coverageAmou
288nt || "N/A", 289 link: linkSelector 290 }); 291 } 292 }); 293 } else { 294 // Page with only one product 295 // Search page with one product too 296 let gaProductName; 297 let image; 298 let gaProduct; 299 let discount = 0; 300 const linkSelector = document.querySelector(areaSelector).querySelector('.evg-btn'); 301 302 if (context.imgSrc1 && !context.secondItemRendering) { 303 gaProductName = context.gaProductName1; 304 if (context.gaProductName1 && context.gaProductName1 !== 'NOT_GA4_PRODUCT') { 305 gaProduct = data[gaProductName]; 306 image = context.imgSrc1.split('/').pop(); 307 308 const basicPrice = 309 context.normalPrice1 && context.normalPrice1 !== '' ? 310 Number(context.normalPrice1.replace(/[^0-9.-]+/g, "")) : 311 +gaProduct.price; 312 313 if (context.discountPrice1 && context.discountPrice1 !== '') { 314 discount = ( 315 basicPrice - 316 Number(context.discountPrice1.replace(/[^0-9.-]+/g, "")) 317 ).toFixed(2); 318 } 319 } 320 } else { 321 gaProductName = context.gaProductName; 322 if (context.gaProductName && context.gaProductName !== 'NOT_GA4_PRODUCT') { 323 gaProduct = data[gaProductName]; 324 if (context.imgSrc && context.imgSrc !== '') {
325 image = context.imgSrc.split('/').pop(); 326 } else if (context.videoId && context.videoId !== '') { 327 image = context.videoId; 328 } 329 330 const basicPrice = 331 context.normalPrice && context.normalPrice !== '' ? 332 Number(context.normalPrice.replace(/[^0-9.-]+/g, "")) : 333 +gaProduct.price; 334 335 if (context.discountPrice && context.discountPrice !== '') { 336 discount = ( 337 basicPrice - 338 Number(context.discountPrice.replace(/[^0-9.-]+/g, "")) 339 ).toFixed(2); 340 } 341 } 342 } 343 344 if (gaProductName !== 'NOT_GA4_PRODUCT') { 345 const variant = (gaProduct.coverType || 'N/A') 346 + '_' + (gaProduct.familyVariant || 'N/A') 347 + '_' + (gaProduct.breakdownVariant || 'N/A') 348 + '_N/A'; 349 350 productData.push({ 351 image: image, 352 campaignId: context.campaign, 353 campaignName: context.header, 354 productId: gaProduct.id, 355 productShortName: gaProduct.shortName, 356 category: gaProduct.category, 357 variant: variant, 358 price: gaProduct.price.toFixed(2), 359 discount: discount, 360 familyVariant: gaProduct.familyVariant || "N/A", 361 breakdownVariant: gaProduct.breakdownVariant || "N/A", 362 fullName: gaProduct.fullName, 363 coverageAmount: gaProduct.coverageAmount || "N/A", 364 link: linkSelector 365 }); 366 } 367 } 368 369 const dataLayer = window.dataLayer || []; 370 if (productData.length > 0) { 371 pushViewPromotion( 372 dataLayer, 373 productData 374 ); 375 addEventListenerForSelectPromotion( 376 dataLayer, 377 productData 378 ); 379 } 380 } 381 }); 382 } 383} 384 385function pushViewPromotion( 386 dataLayer, 387 productData 388) { 389 productData.forEach((product) => { 390 dataLayer.push( 391 createDatalayerContent("view_promotion", product) 392 ) 393 }); 394} 395 396function addEventListenerForSelectPromotion( 397 dataLayer, 398 productData 399) { 400 productData.forEach((product) => { 401 product.link.addEventListener('click', function () { 402 dataLayer.push( 403 createDatalayerContent("select_promotion", product) 404 ) 405 }); 406 }); 407} 408 409function createDatalayerContent( 410 event, 411 product 412) { 413 return { 414 "event": event, 415 "ecommerce": { 416 "creative_name": product.image, 417 "creative_slot": "Interaction Banner", 418 "promotion_id": product.campaignId, 419 "promotion_name": product.campaignName, 420 "items": [ 421 { 422 "item_id": product.productId, 423 "item_name": product.productShortName, 424 "discount": product.discount, 425 "currency": "CHF", 426 "index": 1, 427 "item_brand": "Club", 428 "item_category": product.category, 429 "item_variant": product.variant, 430 "price": product.price, 431 "quantity": 1, 432 "cp_ecom_product_region": "N/A", 433 "cp_ecom_product_club_scope": product.familyVariant, 434 "cp_ecom_product_club_breakdown_service": product.breakdownVariant, 435 "cp_ecom_product_name": product.fullName, 436 "cp_ecom_insurance_coverage_amount": product.coverageAmou
436nt 437 } 438 ] 439 } 440 } 441} 442 443const TEASER_CONTENT_ZONE_ID = '#mcpSectionArea'; 444const CAMPAIGN_CONTROL_EXPERIENCE_NAME = 'Control'; 445// Set timeout to check if Flicker Defender ends it work. 446// If it's the case, it tests if we need to handle pending datalayers 447function addFlickerDefenderTimeout() { 448 if (typeof SalesforceInteractions !== 'undefined' && typeof SalesforceInteractions.getSitemapConfig().global !== 'undefined') { 449 setTimeout(function () { 450 const targetedZone = SalesforceInteractions.mcis.getCampaignResponses()[0]?.payload.contentZone; 451 const sitemapContentZones = SalesforceInteractions.getSitemapConfig().global?.contentZones||[]; 452 const targetedZoneSelector = sitemapContentZones.filter(zone => { 453 return zone.name === targetedZone; 454 })[0]?.selector; 455 456 if (!document.querySelector('style.evergagePersonalizationSections')) { 457 if (!document.querySelector(TEASER_CONTENT_ZONE_ID) || 458 targetedZoneSelector !== TEASER_CONTENT_ZONE_ID || 459 SalesforceInteractions.mcis.getCampaignResponses().length === 0 || 460 SalesforceInteractions.mcis.getCampaignResponses()[0].userGroup === CAMPAIGN_CONTROL_EXPERIENCE_NAME 461 ) { 462 463 // If no active Ajax requests 464 if ($.active === 0) { 465 handlePendingDataLayers(); 466 } else { 467 $(document).ajaxStop(function() { 468 handlePendingDataLayers(); 469 }); 470 } 471 } 472 return; 473 } 474 addFlickerDefenderTimeout(); 475 }, 3000); 476 } 477} 478 479// Function to handle pending data layers 480function handlePendingDataLayers() { 481 while (pendingDataLayers.length > 0) { 482 var pendingDataLayer = pendingDataLayers.shift(); 483 handleDataLayer(pendingDataLayer); 484 } 485} 486 487let pendingDataLayers = []; 488// Add pending datalayer from 4 columns main 489function addPendingDatalayerViewPromotion(dataLayer) { 490 pendingDataLayers.push(dataLayer); 491} 492 493window.addEventListener('load', addFlickerDefenderTimeout);
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.