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