1// This file will be run in the footer on every page. 2// It should be used for content that relates 3// to your site as a whole, it should not be used for 4// plugin-specific js 5 6require(["site_gtm", "plugins_common_custom_ui_watcher", "sv_load!plugins_dynamic_content", "sv_site", "domReady!"], function(gtm, UIWatcher, dynamic, site) { 7 gtm.update(); 8 9 // SEM tracking 10 var uiEvents = new UIWatcher({}, null, document); 11 uiEvents.on("change-page", handlePageChange); 12 uiEvents.on("view-change", handleFilterChange); 13 uiEvents.on("sort-change", handleFilterChange); 14 uiEvents.on("value-change", handleFilterChange); 15 uiEvents.on("toggle-show-more", handleShowMoreToggle); 16 uiEvents.on("item-click", handleItemClick); 17 uiEvents.on("tripbuilder", handleTripbuilder); 18 uiEvents.on("qv-tripbuilder", handleTripbuilder); 19 uiEvents.on("quick-view", handleQuickview); 20 21 // tracking handlers 22 function handleTripbuilder(payload){ 23 var event = payload.eventContext, 24 widget = payload.widgetContext; 25 let ga4Data = { 26 eventName: event?.value === "Add" ? "add_to_favorites" : undefined, 27 itemType: event?.itemType ?? (widget?.name === "Coupons" ? 'offers' : !["quickview", "quick view"].includes(widget?.name.toLowerCase()) ? widget?.name.toLowerCase() : undefined), 28 itemName: event?.refTitle, 29 linkObject: event?.linkObject, 30 itemCategory: event?.crmc, 31 interactionSource: event?.interactionSource, 32 interactionText: event?.interactionText, 33 interactionUrl: event?.interactionUrl, 34 itemCity: event?.itemCity, 35 itemRegion: event?.itemRegion, 36 itemId: event?.itemId, 37 acctId: event?.acctId, 38 itemVariant: event?.itemVariant, 39 price: event?.price, 40 viatorEvent: event?.viatorEvent, 41 } 42 if (event?.crmsc) 43 ga4Data.itemSubcategory = event?.crmsc; 44 if (ga4Data.eventName && ga4Data.itemType && ga4Data.itemName && ga4Data.linkObject) 45 gtm.sendEvents({ tClient_ga4: ga4Data }); 46 } 47 48 function handleQuickview(payload){ 49 var event = payload.eventContext; 50 let ga4Data = { 51 eventName: event?.eventName, 52 itemType: event?.itemType, 53 itemName: event?.itemName ?? event?.refTitle, 54 linkObject: event?.linkObject, 55 itemCategory: event?.crmc, 56 interactionSource: event?.interactionSource, 57 interactionText: event?.interactionText, 58 interactionUrl: event?.interactionUrl, 59 itemCity: event?.itemCity, 60 itemRegion: event?.itemRegion, 61 itemId: event?.itemId, 62 acctId: event?.acctId, 63 itemVariant: event?.itemVariant, 64 price: event?.price, 65 referralType: event?.referralType, 66 viatorEvent: event?.viatorEvent, 67 } 68 if (event?.crmsc) 69 ga4Data.itemSubcategory = event?.crmsc; 70 if (event?.eventName === 'partner_referral') 71 ga4Data.value = 5; 72 if (ga4Data.eventName && ga4Data.itemType && ga4Data.itemName && ga4Data.linkObject) 73 gtm.sendEvents({ tClient_ga4: ga4Data }); 74 } 75 76 function handleShowMoreToggle(payload){ 77 var event = payload.eventContext; 78 let ga4Data = { 79 eventName: "filter", 80 filterType: `${event?.refTitle} | ${event?.displayText}`, 81 itemType: event?.itemType, 82 interactionSource: event?.interactionSource, 83 interactionText: event?.interactionText, 84 } 85 if (ga4Data.eventName && ga4Data.filterType && ga4Data.itemType) 86 gtm.sendEvents({ tClient_ga4: ga4Data }); 87 } 88 89 function handlePageChange(payload){ 90 var event = payload.eventContext; 91 let ga4Data = { 92 eventName: "filter", 93 filterType: `${event?.refTitle} | ${event?.displayText}`, 94 itemType: event?.itemType, 95 interactionSource: event?.interactionSource, 96 interactionText: event?.interactionText, 97 } 98 if (ga4Data.eventName && ga4Data.filterType && ga4Data.itemType) 99 gtm.sendEvents({ tClient_ga4: ga4Data }); 100 } 101 102 function handleItemClick(payload){ 103 var event = payload.eventContext; 104 if (event?.name === "Quickview" || event?.name === "Quick View") { 105 var ga4Data = { 106 eventName: "filter", 107 filterType: "Open Quickview", 108 itemType: event?.itemType, 109 itemName: event?.itemName ?? event?.refTitle, 110 interactionSource: event?.interactionSource, 111 interactionText: event?.interactionText, 112 itemCategory: event?.crmc, 113 itemCity: event?.itemCity, 114 itemRegion: event?.itemRegion, 115 itemId: event?.itemId, 116 acctId: event?.acctId, 117 itemVariant: event?.itemVariant, 118 price: event?.price, 119 viatorEvent: event?.viatorEvent, 120 } 121 if (event?.crmsc) 122 ga4Data.itemSubcategory = event?.crmsc; 123 if (ga4Data.eventName && ga4Data.filterType && ga4Data.itemType) 124 gtm.sendEvents({tClient_ga4: ga4Data}); 125 } else { 126 var ga4Data = { 127 eventName: event?.eventName, 128 widgetName: event?.widgetName, 129 itemType: event?.itemType, 130 itemName: event?.itemName ?? event?.refTitle, 131 linkObject: event?.linkObject, 132 itemCategory: event?.crmc, 133 interactionSource: event?.interactionSource, 134 interactionText: event?.interactionText, 135 interactionUrl: event?.interactionUrl, 136 itemCity: event?.itemCity, 137 itemRegion: event?.itemRegion, 138 itemId: event?.itemId, 139 acctId: event?.acctId, 140 itemVariant: event?.itemVariant, 141 price: event?.price, 142 referralType: event?.referralType, 143 viatorEvent: event?.viatorEvent, 144 } 145 if (event?.crmsc) 146 ga4Data.itemSubcategory = event?.crmsc; 147 if (event?.eventName === 'partner_referral') 148 ga4Data.value = 5; 149 if (ga4Data.eventName && ga4Data.itemType && ga4Data.itemName && ga4Data.linkObject) 150 gtm.sendEvents({tClient_ga4: ga4Data}); 151 } 152 } 153 154 function handleFilterChange (payload) { 155 var event = payload.eventContext; 156 let ga4Data = { 157 eventName: "filter", 158 filterType: `${event?.refTitle} | ${event?.displayText}`, 159 itemType: event?.itemType, 160 interactionSource: event?.interactionSource, 161 interactionText: event?.interactionText, 162 } 163 if (ga4Data.eventName && ga4Data.filterType && ga4Data.itemType) 164 gtm.sendEvents({ tClient_ga4: ga4Data }); 165 } 166 // end SEM tracking 167 168 // Init quickview textboxes 169 if (site.siteConfig.custom[site.site].quickview.active) { 170 const textBoxes = document.querySelectorAll('[data-qv-enabled-textbox]'); 171 172 if (textBoxes.length) { 173 require(['plugins_common_custom_quickview_textbox'], function (qv) {
174 qv.init(); 175 }); 176 } 177 } 178}); 179 180// Lazy-load js for widgets that have the [data-js] attribute on their root node 181(function() { 182 function initDataJs(searchScope, selector = '[data-js]' ){ 183 require(['plugins_common_custom_lazyload'], function(lazyload) { 184 var widgetsToLoad = []; 185 let scope = searchScope ? searchScope : document; 186 var els = scope.querySelectorAll(selector); 187 188 for (var i = 0; i < els.length; i++) { 189 widgetsToLoad.push(els[i]); 190 } 191 192 lazyload.lazy(widgetsToLoad, function(entry, io) { 193 var target = entry.target; 194 var jsFile = target.dataset.js; 195 var guid = target.dataset.guid; 196 197 require([jsFile], function(widget) { 198 widget.init(guid); 199 io.unobserve(target); 200 }); 201 }); 202 }); 203 }; 204 205 initDataJs(); 206 207 let eventToSelectorMap = { 208 // each event needs a slightly different selector 209 "dynamicContentLoaded": "[data-dynamic-chosen] [data-js]", 210 "abContentLoaded": "[data-ab-chosen] [data-js]" 211 } 212 213 Object.entries(eventToSelectorMap).forEach( event => { 214 const [eventName, selector] = event; 215 window.addEventListener(eventName, e => { 216 const collections = document.querySelector(`[data-guid="${e}"]`); 217 initDataJs(collections, selector); 218 }); 219 }); 220})(); 221 222// Wrap core_textbox iframes to prevent horizontal overflow 223(function() { 224 const textBoxes = document.querySelectorAll('[data-sv-core-textbox]'); 225 if (textBoxes.length) { 226 require(['plugins_common_custom_utils'], function(utils) { 227 textBoxes.forEach(utils.wrapIframes); 228 }); 229 } 230})(); 231 232// Remove critical styles from head after all CSS files have loaded 233// This prevents a situation where the critical styles cause an override of the CSS in files 234const criticalStyles = document.querySelector('[data-critical-styles]'); 235if (criticalStyles) { 236 window.addEventListener('load', e =>
236 criticalStyles.remove()); 237} 238 239// update textbox table border styles if border styles were chosen in the CMS 240(function() { 241 const textboxTables = Array.from(document.querySelectorAll('[data-sv-core-textbox] table[border]')); 242 243 if (!textboxTables.length) return; 244 245 textboxTables.forEach((table) => { 246 const rowsAndCells = Array.from(table.querySelectorAll('tr, td')); 247 rowsAndCells.forEach((rc) => { 248 if (table.getAttribute('border')) { 249 rc.style.borderWidth = `${table.getAttribute('border')}px` 250 } 251 if (rc.tagName) { 252 if (table.getAttribute('cellpadding')) { 253 rc.style.padding = `${table.getAttribute('cellpadding')}px` 254 } 255 256 if (table.getAttribute('cellspacing')) { 257 table.style.borderCollapse = 'revert'; 258 rc.style.margin = `${table.getAttribute('cellspacing')}px` 259 } 260 261 } 262 }); 263 }); 264})();
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.