1/** 2 * Port of SA's mobile navigation, this is scaled down as there is only one 3 * level of nesting required for the meganav. 4 */ 5 6$(function() 7{ 8 // Initialize the navigation menu. 9 (function initializeMenu() { 10 11 var $headerButtons = $('#header .buttons'), 12 $navCategories = $headerButtons.find('.nav-categories'), 13 $body = $(document.body), 14 isAnimating = false, 15 currentYPosition = null; 16 17 // Get all the main category links after the mobile sections have been appended. 18 var $navCategoryLinks = $navCategories.find('> ul > li.has-sub:not(.nav-more) > a:not(.js-static-link)'); 19 20 // Mobile: Append mobile category titles to the DOM, using the regular link titles. 21 $navCategoryLinks.each(function() { 22 var linkText = $(this).text(); 23 if (linkText) 24 { 25 $(this).parent().prepend("<h4 class=\"mobile-menu-category-title js-hidden\">" + linkText + "</h4>"); 26 } 27 }); 28 29 // Mobile: Navigate to a sub-category on clicking a main category link. 30 $navCategoryLinks.click(function(e) { 31 32 // Since these links can be clicked on desktop, check if on tablet size and below. 33 // NOTE: There is no screen width check in the other click event listeners, because those elements should only appear on mobile/tablet. 34 if ($(document).width() <= 1120) 35 { 36 // Do not not go to the default link URL. 37 e.preventDefault(); 38 39 if (isAnimating) return; 40 isAnimating = true; 41 42 var $this = $(this); 43 44 $navCategories.removeClass('overflowed'); 45 $navCategories.children('ul').toggleClass('offscreen'); 46 47 $this.siblings('.nav-sub-container').addClass('mobile-category-active overflowed'); 48 $this.siblings('.mobile-menu-category-title').removeClass('js-hidden'); 49 50 setTimeout(function () { 51 isAnimating = false; 52 }, 500); 53 } 54 }); 55 56 // Mobile: Navigate back to main categories on clicking the top titles in the sub menus. 57 $navCategories.find('.mobile-menu-category-title').click(function() { 58 if (!isAnimating) 59 { 60 var $this = $(this); 61 62 isAnimating = true; 63 64 $navCategories.children('ul').removeClass('offscreen'); 65 $navCategories.addClass('overflowed'); 66 67 setTimeout (function() { 68 $this.siblings('.nav-sub-container').removeClass('mobile-category-active'); 69 $this.addClass('js-hidden'); 70 isAnimating = false; 71 }, 500); 72 } 73 }); 74 75 // Mobile: Toggle the mobile menu on clicking the hamburger button, the mobile close button, or the mobile overlay (area outside the menu). 76 $('.header-mobile-menu-button, .mobile-menu-close-button, #mobile-menu-overlay').on('click', function () { 77 78 // Don't do anything if animating. 79 if (isAnimating) return; 80 81 var menuIsActive = $headerButtons.hasClass('mobile-menu-active'); 82 83 $('#mobile-menu-overlay').toggle(); 84 isAnimating = true; 85 86 // If menu is open, close it. 87 if (menuIsActive) 88 { 89 $headerButtons.removeClass('mobile-menu-active'); 90 91 // Let the body scroll. 92 window.requestAnimationFrame(function() 93 { 94 // Add a 'mobile-menu-open' class to the body to make it non-scrollable. 95 $body.removeClass('mobile-menu-open') 96 .css({ 97 top: '' 98 }); 99 100 window.scrollTo({ 101 top: currentYPosition, 102 behavior: 'instant' 103 }); 104 }); 105 106 setTimeout(function() { 107 // Reset elements after the menu has finished closing. 108 $headerButtons.removeClass('mobile-menu-visible'); 109 $navCategories.children('ul').removeClass('offscreen'); 110 $navCategories.find('.nav-sub-container').removeClass('mobile-category-active'); 111 }, 500); 112 } 113 else 114 { 115 // Toggle the visibility of the mobile menu, then scroll it into view. 116 $headerButtons.addClass('mobile-menu-visible'); 117 setTimeout(function() { 118 $headerButtons.addClass('mobile-menu-active'); 119 }, 10); 120 121 window.requestAnimationFrame(function() 122 { 123 // recording the current window position 124 currentYPosition = window.scrollY; 125 // Add a 'mobile-menu-open' class to the body to make it non-scrollable. 126 $body.addClass('mobile-menu-open') 127 .css({ 128 top: currentYPosition * -1 + 'px' 129 }); 130 }); 131 132 // Making the menu scrollable since this class was possibly removed. 133 $navCategories.addClass('overflowed'); 134 } 135 136 setTimeout(function() { 137 isAnimating = false;
138 }, 500); 139 }); 140 })(); 141}); 142// All tracking related functionality is contained here 143// mostly ported from SA. tweaked here and there 144var smartasset = smartasset || {}; 145 146smartasset.analytics = smartasset.analytics || {}; 147 148/** 149 * Initialize mixpanel link tracking 150 */ 151smartasset.analytics.initializeTracking = function() 152{ 153 // track the page view 154 smartasset.analytics.trackPageView(true); 155 156 // track the page view in facebook 157 smartasset.analytics.facebookTrack('ViewContent'); 158}; 159 160/** 161 * Track page view on MixPanel and GA 162 * @param noReferer -- boolean. Set to TRUE if you do not want to override the referer 163 */ 164smartasset.analytics.trackPageView = function(noReferer) 165{ 166 if ($.type(noReferer) !== "boolean") 167 { 168 noReferer = false; 169 } 170 171 if (IS_DEV) 172 { 173 console.log("Tracking page-view // noReferer: " + noReferer); 174 } 175 176 // send Google "pageview", a custom Google event 177 if (typeof ga === 'function') 178 { 179 if (!noReferer) 180 { 181 var url = prefixUrl(smartasset.page.originalUrl); 182 ga('set', 'referrer', url); 183 } 184 185 // "pageview", "page", and "title" are Google Analytics keywords 186 ga('send', 'pageview', { 187 'page': window.location.pathname, 188 'title': document.title 189 }); 190 191 if (IS_DEV) 192 { 193 console.log('PageView Track: ', window.location.pathname); 194 } 195 196 // Broadcast our "Page Loaded" event to all services 197 smartasset.analytics.track('Page Loaded'); 198 } 199}; 200 201/** 202 * Facebook tracking 203 * @param label: custom id to send to FB for tracking 204 * @param customData: a custom object sent with specifications about the event 205 */ 206 207smartasset.analytics.facebookTrack = function(label, customData) 208{ 209 // First check if the facebook tracker is instantiated 210 if (typeof fbq === 'function' && $.type(label) === 'string') 211 { 212 // Set the event type 213 var eventType; 214 if (label === 'ViewContent') 215 { 216 eventType = 'track'; 217 } 218 else 219 { 220 eventType = 'trackCustom'; 221 } 222 223 // Check if there is custom data 224 if ($.type(customData) !== 'object') 225 { 226 customData = {}; 227 } 228 229 // Include UTM Campaign params 230 var queryParams = $.queryParams(); 231 232 customData.utmsource = queryParams.utm_source ? queryParams.utm_source : undefined; 233 customData.utmcampaign = queryParams.utm_campaign ? queryParams.utm_campaign : undefined; 234 customData.utmmedium = queryParams.utm_medium ? queryParams.utm_medium : undefined; 235 customData.utmcontent = queryParams.utm_content ? queryParams.utm_content : undefined; 236 customData.utmterm = queryParams.utm_term ? queryParams.utm_term : undefined; 237 238 // Track the event 239 fbq(eventType, label, customData); 240 241 if (IS_DEV) { 242 console.log('Facebook Track: ' + label, customData); 243 } 244 } 245}; 246 247/** 248 * Event Tracker 249 * @param mpEvent: name of the MP event 250 * @param mpParams: additional optional items to include in the MP event 251 */ 252smartasset.analytics.eventTrack = function(mpEvent, mpParams) 253{ 254 if ($.type(mpEvent) === 'string') 255 { 256 // Track in Google Analytics (GA) and Mixpanel (MP) 257 if ($.type(mpParams) !== 'object') 258 { 259 mpParams = {}; 260 } 261 262 smartasset.analytics.track(mpEvent, mpParams); 263 } 264}; 265 266/** 267 * PLEASE READ NOTES IN THE CODE ON HOW TO USE THIS! 268 * track("eventName", { value: 4, color: "Blue"}) 269 * 270 * @param {string} eventKey 271 * @param {Object=} data optional object with parameters for the event. 272 * @param {boolean} skipMixpanel if true, skips sending data to Mixpanel 273 */ 274smartasset.analytics.track = function(eventKey, data, skipMixpanel) 275{ 276 var skipGoogleAnalytics = false, 277 eventsToSkipInGA = ['Page Loaded']; 278 279 if (eventsToSkipInGA.indexOf(eventKey) !== -1) 280 { 281 skipGoogleAnalytics = true; 282 } 283 284 // Omit events from MP 285 if (typeof skipMixpanel !== 'boolean') 286 { 287 skipMixpanel = false;
288 } 289 290 if (IS_DEV) 291 { 292 console.log('Tracking Event: ', eventKey, data); 293 } 294 295 // instantiate a copy of the data parameter that we can mutate 296 var properties = (typeof data === 'object') ? data : {}; 297 298 // avoid logging an invalid key 299 if (!eventKey || typeof eventKey !== 'string') 300 { 301 console.error('Error - key is invalid'); 302 return; 303 } 304 305 properties.page = window.location.pathname; 306 307 // Raise an event within the window. This is useful for detecting 308 // events in other code. 309 // NOTE: replace "createEvent" with CustomEvent when we deprecate IE8 310 if (document.createEvent && document.dispatchEvent) 311 { 312 var evt = document.createEvent('Event'); 313 evt.initEvent(eventKey, true, true); 314 document.dispatchEvent(evt); 315 } 316 317 // Send event to Mixpanel 318 if (!skipMixpanel && typeof mixpanel === "object" && typeof mixpanel.track === 'function') 319 { 320 mixpanel.track(eventKey, properties); 321 } 322 323 // Send event to Google Analytics 324 if (!skipGoogleAnalytics && typeof ga === 'function') 325 { 326 ga('send', 'event', eventKey, eventKey, properties); 327 } 328}; 329
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.