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