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(["jquery", "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({}, $, 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("scroll-into-view", handleScrollIntoView); 17 uiEvents.on("item-click", handleItemClick); 18 uiEvents.on("map-pin-click", handleMapPinClick); 19 uiEvents.on("tripbuilder", handleTripbuilder); 20 uiEvents.on("qv-tripbuilder", handleTripbuilder); 21 uiEvents.on("quick-view", handleQuickview); 22 23 // tracking handlers 24 function handleTripbuilder(payload){ 25 var event = payload.eventContext, 26 widget = payload.widgetContext; 27 var eventCatName = widget.name; 28 switch (event.detailType) { 29 case "listings": 30 eventCatName = "Listings"; 31 break; 32 case "events": 33 eventCatName = "Events"; 34 break; 35 case "offers": 36 eventCatName = "Coupons"; 37 break; 38 default: 39 eventCatName = widget.name; 40 } 41 var data = { 42 eventCategory: eventCatName, 43 eventAction: event.name +" - "+ event.value, 44 eventLabel: event.refTitle, 45 ot: event.type, 46 ltxt: event.displayText, 47 nonInteraction: false 48 } 49 if (data.eventCategory && data.eventAction && data.eventLabel) 50 gtm.sendEvents({tClient: data}); 51 52 // GA4 53 let crmCatSubcat = event?.crmc; 54 if (crmCatSubcat && event.crmsc) { 55 crmCatSubcat += " | " + event.crmsc; 56 } 57 let ga4Data = { 58 eventName: event.value ==="Add" ? "add_to_favorites" : undefined, 59 itemType: widget.name ==="Coupons"? 'offers' : widget.name.toLowerCase() !== "quick view" ? widget.name.toLowerCase() : event.itemType, 60 itemName: event.refTitle, 61 linkObject: event.linkObject, 62 crmCatSubcat: crmCatSubcat 63 } 64 65 if (ga4Data.eventName && ga4Data.itemType && ga4Data.itemName) 66 gtm.sendEvents({tClient_ga4: ga4Data}); 67 } 68 69 function handleQuickview(payload){ 70 var event = payload.eventContext, 71 widget = payload.widgetContext; 72 var eventCatName = widget.name; 73 switch (event.detailType) { 74 case "listings": 75 eventCatName = "Listings"; 76 break; 77 case "events": 78 eventCatName = "Events"; 79 break; 80 case "offers": 81 eventCatName = "Coupons"; 82 break; 83 default: 84 eventCatName = widget.name; 85 } 86 var data = { 87 eventCategory: eventCatName, 88 eventAction: event.linkTo, 89 eventLabel: event.refTitle, 90 ot: event.type, 91 ltxt: event.displayText, 92 nonInteraction: false 93 } 94 if (data.eventCategory && data.eventAction && data.eventLabel) 95 gtm.sendEvents({tClient: data}); 96 97 // GA4 98 let crmCatSubcat = event?.crmc; 99 if (event?.v_crmc) { 100 crmCatSubcat = event.v_crmc; 101 } else if (crmCatSubcat && event.crmsc) { 102 crmCatSubcat += " | " + event.crmsc; 103 } 104 105 let ga4Data = { 106 eventName: event.eventName, 107 itemType: event.itemType, 108 itemName: event.itemName ?? event.refTitle, 109 linkObject: event.linkObject, 110 crmCatSubcat: crmCatSubcat 111 } 112 if (ga4Data.eventName && ga4Data.itemType && ga4Data.itemName && ga4Data.linkObject) 113 gtm.sendEvents({tClient_ga4: ga4Data}); 114 } 115 116 function handleScrollIntoView(payload){ 117 var event = payload.eventContext, 118 widget = payload.widgetContext; 119 var data = { 120 eventCategory: widget.name, 121 eventAction: event.name, 122 eventLabel: "Widget - "+widget.name, 123 nonInteraction: true 124 } 125 if (data.eventCategory && data.eventAction && data.eventLabel) 126 gtm.sendEvents({tClient: data}); 127 } 128 129 function handleShowMoreToggle(payload){ 130 var event = payload.eventContext, 131 widget = payload.widgetContext; 132 var data = { 133 eventCategory: widget.name, 134 eventAction: event.name, 135 eventLabel: event.displayText+" | "+event.refTitle, 136 nonInteraction: false 137 } 138 if (data.eventCategory && data.eventAction && data.eventLabel) 139 gtm.sendEvents({tClient: data}); 140 141 // GA4 142 let ga4Data = { 143 eventName: "filter", 144 filterType: event.displayText+" | "+event.refTitle, 145 itemType: event.itemType
146 } 147 if (ga4Data.eventName && ga4Data.filterType && ga4Data.itemType) 148 gtm.sendEvents({tClient_ga4: ga4Data}); 149 } 150 151 function handlePageChange(payload){ 152 var event = payload.eventContext, 153 widget = payload.widgetContext; 154 var data = { 155 eventCategory: widget.name, 156 eventAction: event.name, 157 eventLabel: event.refTitle+" | "+event.displayText, 158 nonInteraction: false 159 } 160 if (data.eventCategory && data.eventAction && data.eventLabel) 161 gtm.sendEvents({tClient: data}); 162 163 // GA4 164 let ga4Data = { 165 eventName: "filter", 166 filterType: event.refTitle+" | "+event.displayText, 167 itemType: event.itemType 168 } 169 if (ga4Data.eventName && ga4Data.filterType && ga4Data.itemType) 170 gtm.sendEvents({tClient_ga4: ga4Data}); 171 } 172 173 function handleItemClick(payload){ 174 var event = payload.eventContext, 175 widget = payload.widgetContext; 176 var data = { 177 eventCategory: widget.name, 178 eventAction: event.linkTo, 179 eventLabel: event.refTitle, 180 ot: event.type, 181 ltxt: event.displayText, 182 nonInteraction: false 183 } 184 /* alter action for once there */ 185 if (event.recId !== undefined && event.recId.toString().indexOf("ot") > -1) { 186 data.eventAction = "OnceThere"; 187 } 188 if (data.eventCategory && data.eventAction && data.eventLabel) 189 gtm.sendEvents({tClient: data}); 190 191 // GA4 192 let crmCatSubcat = event?.crmc; 193 if (event?.v_crmc) { 194 crmCatSubcat = event.v_crmc; 195 } else if (crmCatSubcat && event.crmsc) { 196 crmCatSubcat += " | " + event.crmsc; 197 } 198 199 if (event?.name === "Quick View") { 200 var ga4Data = { 201 eventName: "filter", 202 filterType: "Open Quick View", 203 itemType: event.itemType, 204 itemName: event.itemName ?? event.refTitle, 205 } 206 if (ga4Data.eventName && ga4Data.filterType && ga4Data.itemType) 207 gtm.sendEvents({tClient_ga4: ga4Data}); 208 } 209 else { 210 var ga4Data = { 211 eventName: event.eventName, 212 itemType: event.itemType, 213 itemName: event.itemName ?? event.refTitle, 214 linkObject: event.linkObject, 215 crmCatSubcat: crmCatSubcat 216 } 217 if (ga4Data.eventName && ga4Data.itemType && ga4Data.itemName && ga4Data.linkObject) 218 gtm.sendEvents({tClient_ga4: ga4Data}); 219 } 220 } 221 222 function handleMapPinClick(payload){ 223 var event = payload.eventContext, 224 widget = payload.widgetContext; 225 var data = { 226 eventCategory: widget.name, 227 eventAction: event.name, 228 eventLabel: event.refTitle, 229 ot: event.type, 230 nonInteraction: false 231 } 232 if (data.eventCategory && data.eventAction && data.eventLabel) 233 gtm.sendEvents({tClient: data}); 234 } 235 236 function handleFilterChange (payload) { 237 var event = payload.eventContext, 238 widget = payload.widgetContext; 239 var data = { 240 eventCategory: widget.name, 241 eventAction: "Filter Change", 242 eventLabel: event.refTitle+" | "+event.displayText, 243 nonInteraction: false 244 } 245 if (data.eventCategory && data.eventAction && data.eventLabel) 246 gtm.sendEvents({tClient: data}); 247 248 // GA4 249 let ga4Data = { 250 eventName: "filter", 251 filterType: event.refTitle+" | "+event.displayText, 252 itemType: event.itemType 253 } 254 if (ga4Data.eventName && ga4Data.filterType && ga4Data.itemType) 255 gtm.sendEvents({tClient_ga4: ga4Data}); 256 } 257 // end SEM tracking 258 259 // Init quickview textboxes 260 if (site.siteConfig.custom[site.site].quickview.active) { 261 const textBoxes = document.querySelectorAll('[data-qv-enabled-textbox]'); 262 263 if (textBoxes.length) { 264 require(['plugins_common_custom_quickview_textbox'], function (qv) { 265 qv.init(); 266 }); 267 } 268 } 269}); 270 271// Lazy-load FontAwesome 272(function() { 273 var css = document.createElement('link'); 274 css.href = 'https://use.fontawesome.com/releases/v5.14.0/css/all.css'; 275 css.rel = 'stylesheet'; 276 css.type = 'text/css'; 277 document.getElementsByTagName('head')[0].appendChild(css); 278})(); 279 280// Lazy-load js for widgets that have the [data-js] attribute on their root node 281(function() { 282 function initDataJs(searchScope, selector = '[data-js]' ){ 283 require(['plugins_common_custom_lazyload'], function(lazyload) { 284 var widgetsToLoad = []; 285 let scope = searchScope ? searchScope : document; 286 var els = scope.querySelectorAll(selector); 287 288 for (var i = 0; i < els.length; i++) { 289 widgetsToLoad.push(els[i]); 290 } 291 292 lazyload.lazy(widgetsToLoad, function(entry, io) { 293 var target = entry.target; 294 var jsFile = target.dataset.js; 295 var guid = target.dataset.guid; 296 297 require([jsFile], function(widget) { 298 widget.init(guid); 299 io.unobserve(target); 300 }); 301 }); 302 }); 303 }; 304 305 initDataJs(); 306 307 let eventToSelectorMap = { 308 // each event needs a slightly different selector 309 "dynamicContentLoaded": "[data-dynamic-chosen] [data-js]", 310 "abContentLoaded": "[data-ab-chosen] [data-js]" 311 } 312
313 Object.entries(eventToSelectorMap).forEach( event => { 314 const [eventName, selector] = event; 315 window.addEventListener(eventName, e => { 316 const collections = document.querySelector(`[data-guid="${e}"]`); 317 initDataJs(collections, selector); 318 }); 319 }); 320})(); 321 322// Wrap core_textbox iframes to prevent horizontal overflow 323(function() { 324 const textBoxes = document.querySelectorAll('[data-sv-core-textbox]'); 325 if (textBoxes.length) { 326 require(['plugins_common_custom_utils'], function(utils) { 327 textBoxes.forEach(utils.wrapIframes); 328 }); 329 } 330})(); 331 332// Remove critical styles from head after all CSS files have loaded 333// This prevents a situation where the critical styles cause an override of the CSS in files 334const criticalStyles = document.querySelector('[data-critical-styles]'); 335if (criticalStyles) { 336 window.addEventListener('load', e => criticalStyles.remove()); 337}
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.