1(function ($, undefined) { 2 $.cv = $.cv || {}; 3 $.cv.css = $.cv.css || {}; 4 $.cv.css.tracker = $.cv.css.tracker || {}; 5 $.cv.css.tracker.implementation = $.cv.css.tracker.implementation || {}; 6 /** 7 * Google Analytis 4 Issues with dataLayer.push(); 8 * 9 * WARNING: this should be use for ALL push()es to the data layer for 10 * Google Analytics 4. 11 * 12 * For some reason we can't just dataLayer.push() an object or an array 13 * of the correct structure, the only way that the gtag stuff works 14 * is if we have a function we call and we push the 'arguments' value 15 * onto the dataLayer. Thus we need to implement the push abstraction 16 * here to permit pushing 'arguments' onto the data layer for GA4. 17 */ 18 $.cv.css.tracker.implementation.push = function () { 19 var _ = []; 20 for (var _i = 0; _i < arguments.length; _i++) { 21 _[_i] = arguments[_i]; 22 } 23 var dataLayer = $.cv.css.tracker.getDataLayer(); 24 dataLayer.push(arguments); 25 }; 26 function rename(obj, oldName, newName) { 27 var oldValue = obj[oldName]; 28 if (oldValue != null) { 29 obj[newName] = obj[oldName]; 30 delete obj[oldName]; 31 } 32 } 33 function splitCategory(category) { 34 category = category || ''; 35 return _.filter(category.split(/\//), function (item) { return item != null && item.length > 0; }); 36 } 37 $.cv.css.tracker.implementation.splitCategory = splitCategory; 38 function mapProduct(product) { 39 // Create a copy so we don't modify the original object. 40 var mappedProduct = JSON.parse(JSON.stringify(product)); 41 rename(mappedProduct, 'name', 'item_name'); 42 rename(mappedProduct, 'id', 'item_id'); 43 rename(mappedProduct, 'brand', 'item_brand'); 44 rename(mappedProduct, 'variant', 'item_variant'); 45 rename(mappedProduct, 'position', 'index'); 46 var categories = splitCategory(mappedProduct.category); 47 mappedProduct.item_category = categories[0]; 48 mappedProduct.item_category2 = categories[1]; 49 mappedProduct.item_category3 = categories[2]; 50 mappedProduct.item_category4 = categories[3]; 51 mappedProduct.item_category5 = categories[4]; 52 mappedProduct.price = parseFloat(mappedProduct.price); 53 mappedProduct.index = parseInt(mappedProduct.index); 54 delete mappedProduct.category; 55 return mappedProduct; 56 } 57 $.cv.css.tracker.implementation.mapProduct = mapProduct; 58 function getReturnedValue(pushData) { 59 var _a; 60 // WARNING: 61 // When AnalyticsVersion does not contain Universal Analytics (selected version is GA4). 62 // Associated implementations modify and trigger the gtag() style call and return undefined on purpose. 63 // The call site will not do anything with the undefined or event callback only return value; 64 if ($.cv.css.tracker.options.AnalyticsVersion !== SiteTrackerAnalyticsVersion.GA4) { 65 return pushData; 66 } 67 // It uses for finialise the event callback function if exists, it doesn't actually push to data layer 68 if (pushData.hasOwnProperty(TrackerEventCallbackName)) { 69 return _a = {}, _a[TrackerEventCallbackName] = pushData[TrackerEventCallbackName], _a; 70 } 71 return undefined; 72 } 73 function getLineItemsData() { 74 var lines = _.filter($.cv.css.localGetCurrentOrderLines(), function (p) { 75 return $.cv.util.hasValue(p, "Product[0].ProductCode"); 76 }); 77 var itemsValue = 0; 78 var lineItems = _.map(lines, function (line) { 79 itemsValue += line.OrderLineAmount; 80 return mapProduct({ 81 name: line.Description, 82 id: line.StockCode, 83 brand: line.Product[0].BrandText, 84 category: line.Product[0].CategoryHierarchy, 85 quantity: line.OrderedQty, 86 price: line.ItemPrice, 87 position: line.LineSeq 88 }); 89 }); 90 return { itemsValue: parseFloat(itemsValue.toFixed($.cv.css.tracker.options.DecimalPlaces)), lineItems: lineItems }; 91 } 92 $.cv.css.tracker.implementation.productImpressions = function (impressions) { 93 var returnedValue = getReturnedValue(impressions); 94 var isProduct = $.cv.util.hasValue(impressions, 'ecommerce.detail.products'); 95 var items = impressions.ecommerce.impressions || impressions.ecommerce.detail.products; 96 var payload = { 97 value: 0, 98 currency: impressions.ecommerce.currencyCode, 99 items: [] 100 }; 101 var listName = impressions.ecommerce.impressions != undefined && impressions.ecommerce.impressions.length > 0 102 ? impressions.ecommerce.impressions[0].list 103 : impressions.ecommerce.detail.actionField.list; 104 var eventName = isProduct ? 'view_item' : 'view_item_list'; 105 var itemsValue = 0; 106 var mappedItems = []; 107 // NOTE: we need to map some fields that have had field name 108 // renames from prior UA dimensions. All other fields 109 // we be retained as they were as they are valid in GA4. 110 for (var i = 0; i < items.length; i++) { 111 var product = items[i]; 112 var mappedProduct = mapProduct(product); 113 if (isProduct) { 114 mappedProduct.index = 0; 115 mappedProduct.item_list_name = listName; 116 itemsValue += mappedProduct.price; 117 } 118 mappedItems.push(mappedProduct); 119 } 120 payload.items = mappedItems; 121 payload.value = itemsValue; 122 // view_item_list does not have the 'value' field 123 if (!isProduct) { 124 delete payload.value; 125 // TODO: danielv this is to handle the campaign list type but we have't decided yet how it would work
126 // Will need to revisit once we have a better understanding of how the campaign list type will work 127 //if (listName.startsWith($.cv.css.tracker.options.CampaignListPrefix)) { 128 // payload.promotion_id = $.cv.css.tracker.options.CampaignCode; 129 // payload.promotion_name = listName; 130 // eventName = 'view_promotion'; 131 //} else { 132 // payload.item_list_name = listName; 133 //} 134 payload.item_list_name = listName; 135 } 136 // Trigger the event 137 $.cv.css.tracker.implementation.push('event', eventName, payload); 138 return returnedValue; 139 }; 140 $.cv.css.tracker.implementation.productClick = function (pushData) { 141 var returnedValue = getReturnedValue(pushData); 142 var listName = pushData.ecommerce.click.actionField.list; 143 var items = pushData.ecommerce.click.products; 144 var payload = { 145 value: 0, 146 currency: pushData.ecommerce.currencyCode, 147 item_list_name: listName, 148 items: [] 149 }; 150 var itemsValue = 0; 151 for (var i = 0; i < items.length; i++) { 152 var product = items[i]; 153 var mappedProduct = mapProduct(product); 154 itemsValue += mappedProduct.price; 155 payload.items.push(mappedProduct); 156 } 157 payload.value = itemsValue; 158 // Trigger the event 159 $.cv.css.tracker.implementation.push('event', 'select_item', payload); 160 return returnedValue; 161 }; 162 $.cv.css.tracker.implementation.bannerClick = function (pushData) { 163 return processBannerData(pushData, 'select_banner'); 164 }; 165 $.cv.css.tracker.implementation.bannerView = function (pushData) { 166 return processBannerData(pushData, 'view_banner'); 167 }; 168 function processBannerData(pushData, eventName) { 169 var returnedValue = getReturnedValue(pushData); 170 var payload = { 171 banner_description: pushData.Description 172 }; 173 $.cv.css.tracker.implementation.push('event', eventName, payload); 174 return returnedValue; 175 } 176 $.cv.css.tracker.implementation.addToCart = function (products, listName) { 177 var returnedValue = getReturnedValue(products); 178 var payload = { 179 value: 0, 180 currency: $.cv.css.tracker.options.CurrencyCode, 181 items: [] 182 }; 183 var itemsValue = 0; 184 for (var i = 0; i < products.length; i++) { 185 var product = products[i]; 186 var mappedProduct = mapProduct(product); 187 if (!$.cv.css.tracker.options.PageFlags.IsProductDisplayCampaignPage) { 188 mappedProduct.item_list_name = listName; 189 } 190 if (!$.cv.css.tracker.options.HidePricing) { 191 itemsValue += mappedProduct.quantity * mappedProduct.price; 192 } 193 payload.items.push(mappedProduct); 194 } 195 // This is to avoid the case that itemsValue is calculated with multiple decimal places 196 payload.value = parseFloat(itemsValue.toFixed($.cv.css.tracker.options.DecimalPlaces)); 197 // TODO: danielv this is to handle the campaign list type but we have't decided yet how it would work 198 // Will need to revisit once we have a better understanding of how the campaign list type will work 199 //if (listName.startsWith($.cv.css.tracker.options.CampaignListPrefix)) { 200 // payload.promotion_id = $.cv.css.tracker.options.CampaignCode; 201 // payload.promotion_name = listName; 202 // $.cv.css.tracker.implementation.push('event', 'select_promotion', payload); 203 //} else { 204 // $.cv.css.tracker.implementation.push('event', 'add_to_cart', payload); 205 //} 206 $.cv.css.tracker.implementation.push('event', 'add_to_cart', payload); 207 return returnedValue; 208 }; 209 $.cv.css.tracker.implementation.removeFromCart = function (products) { 210 var returnedValue = getReturnedValue(products); 211 var payload = { 212 value: 0, 213 currency: $.cv.css.tracker.options.CurrencyCode, 214 items: [] 215 }; 216 var listName = $.cv.css.tracker.options.ListTypes.CartLines; 217 var hidePricing = $.cv.css.tracker.options.HidePricing; 218 for (var i = 0; i < products.length; i++) { 219 var product = products[i]; 220 var mappedProduct = mapProduct(product); 221 mappedProduct.item_list_name = listName; 222 if (!hidePricing) { 223 payload.value += mappedProduct.price * mappedProduct.quantity; 224 } 225 payload.items.push(mappedProduct); 226 } 227 // Trigger the event 228 $.cv.css.tracker.implementation.push('event', 'remove_from_cart', payload); 229 return returnedValue; 230 };
231 $.cv.css.tracker.implementation.checkout = function (pushData) { 232 var returnedValue = getReturnedValue(pushData); 233 var lines = getLineItemsData(); 234 var step = pushData.ecommerce.checkout.actionField.step; 235 var checkoutData = { 236 value: lines.itemsValue, 237 currency: $.cv.css.tracker.options.CurrencyCode, 238 step: step, 239 items: lines.lineItems, 240 }; 241 if (step == 1) { 242 $.cv.css.tracker.implementation.push("event", "view_cart", checkoutData); 243 } 244 if (step == 2) { 245 $.cv.css.tracker.implementation.push("event", "begin_checkout", checkoutData); 246 } 247 return returnedValue; 248 }; 249 $.cv.css.tracker.implementation.checkoutOption = function (pushData) { 250 var returnedValue = getReturnedValue(pushData); 251 var lines = getLineItemsData(); 252 var step = pushData.ecommerce.checkout_option.actionField.step; 253 if (step == 3) { 254 $.cv.css.tracker.implementation.push("event", "add_shipping_info", { 255 value: lines.itemsValue, 256 currency: $.cv.css.tracker.options.CurrencyCode, 257 shipping_tier: pushData.ecommerce.checkout_option.actionField.option, 258 step: step, 259 items: lines.lineItems, 260 }); 261 } 262 if (step == 4) { 263 $.cv.css.tracker.implementation.push("event", "add_payment_info", { 264 value: lines.itemsValue, 265 currency: $.cv.css.tracker.options.CurrencyCode, 266 payment_type: pushData.ecommerce.checkout_option.actionField.option, 267 step: step, 268 items: lines.lineItems, 269 }); 270 } 271 return returnedValue; 272 }; 273 $.cv.css.tracker.implementation.transactionProducts = function (pushData) { 274 var returnedValue = getReturnedValue(pushData); 275 var products = pushData.ecommerce.purchase.products; 276 var itemsValue = 0; 277 var payload = { 278 transaction_id: pushData.ecommerce.purchase.actionField.id, 279 value: 0, 280 tax: pushData.ecommerce.purchase.actionField.tax, 281 shipping: pushData.ecommerce.purchase.actionField.shipping, 282 currency: pushData.ecommerce.currencyCode, 283 items: [] 284 }; 285 for (var i = 0; i < products.length; i++) { 286 var product = products[i]; 287 var mappedProduct = mapProduct(product); 288 itemsValue += mappedProduct.price * mappedProduct.quantity; 289 payload.items.push(mappedProduct); 290 } 291 payload.value = parseFloat(itemsValue.toFixed($.cv.css.tracker.options.DecimalPlaces)); 292 // Trigger the event 293 $.cv.css.tracker.implementation.push('event', 'purchase', payload); 294 return returnedValue; 295 }; 296 $.cv.css.tracker.implementation.productSearch = function (pushData) { 297 var returnedValue = getReturnedValue(pushData); 298 var payload = { 299 search_term: pushData.searchTerm, 300 search_type: pushData.searchType, 301 }; 302 // Trigger the event 303 $.cv.css.tracker.implementation.push('event', 'search', payload); 304 return returnedValue; 305 }; 306 $.cv.css.tracker.implementation.addToWishList = function (pushData, listName) { 307 var returnedValue = getReturnedValue(pushData); 308 var payload = { 309 value: 0, 310 currency: $.cv.css.tracker.options.CurrencyCode, 311 items: [] 312 }; 313 var mappedProduct = mapProduct(pushData); 314 mappedProduct.item_list_name = listName; 315 payload.items.push(mappedProduct); 316 payload.value = mappedProduct.price; 317 // Trigger the event 318 $.cv.css.tracker.implementation.push('event', 'add_to_wishlist', payload); 319 return returnedValue; 320 }; 321})(jQuery);
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.