1var smartasset = smartasset || {}; 2 3smartasset.home = smartasset.home || {}; 4 5/** 6 * Sends an event for the form in the hero component's submit event 7 * only if the form input is valid. 8 * 9 * @param {element} form - the form element that is being submitted 10 * 11 * @return {boolean} true if the form is valid, false otherwise. 12 */ 13smartasset.home.submitHeroFormEvent = function(form) 14{ 15 var $input = $(form).children('input'); 16 var zip = $input.val(); 17 18 if (zip && zip.match($input.attr('pattern'))) 19 { 20 smartasset.analytics.eventTrack('home-getstarted-success'); 21 return true; 22 } 23 24 return false; 25}; 26 27/** 28 * Restricts the input from a user's keyboard from entering non numeric characters 29 * in a form field, the form field is also limited to a fixed number of characters. 30 * 31 * This uses KeyboardEvent.key preferably since KeyboardEvent.keyCode is deprecated 32 * However it does fallback to KeyboardEvent.keyCode if KeyboardEvent.key is not available. 33 * 34 * The logic here accounts for both use cases. 35 * 36 * @param {number} max - maximum number of characters allowed. Defaults to 5 characters. 37 * @param {element} $input - jQuery input element whose keydown events we'll be handling. 38 */ 39smartasset.home.restrictNumberInput = function ($input, max) { 40 var selectAll = false; 41 42 var maxCharacters = max || 5; 43 // Allowed keyCodes and key values for modifier keys: backspace, delete, tab, escape and enter. 44 var allowedModifierKeysAndKeycodes = 45 [46, 8, 9, 27, 13, 'Escape', 'Esc', 'Backspace', 'Delete', 'Del', 'Tab', 'Enter']; 46 // Allowed key values for directionals, omitting keyCodes because we need to check for a range. 47 var allowedDirectionalKeys = ['Left', 'ArrowLeft', 'Right', 'ArrowRight', 'Home', 'End']; 48 // Copy & Cut key values and keyCodes 49 var copyOrCutKeysAndKeycodes = ['c', 68, 'x', 88]; 50 // 'a' character key value & keyCode 51 var selectAllKeyAndKeycode = ['a', 65]; 52 53 $input.on('keydown', function (e) { 54 var inputValue = $(this).val(); 55 // Prefer KeyboardEvent.key but fallback to KeyboardEvent.keyCode if needed. 56 var key = e.key || e.keyCode; 57 58 // Allow: left, right, home, end keys 59 if ($.inArray(key, allowedModifierKeysAndKeycodes) !== -1 || 60 // Allow: Ctrl+c || Cmd+c || Ctrl+x || Cmd+x 61 ($.inArray(key, copyOrCutKeysAndKeycodes) && (e.ctrlKey || e.metaKey)) || 62 // Allow: home || end || left || right 63 (($.inArray(key, allowedDirectionalKeys) !== -1) || (key >= 35 && key <= 39))) 64 { 65 // let the keypress happen. 66 } 67 else if ($.inArray(key, selectAllKeyAndKeycode) && (e.ctrlKey || e.metaKey)) 68 { 69 // Ctrl+A || Cmd+A: Set flag 70 selectAll = true; 71 } 72 else 73 { 74 // Ensure that it is a number and stop the keypress if it is not. 75 // (regex: match only for strings with a single digit or Num and a single digit) 76 if ((key.match(/^\d\b|Num\d$/) === null) || 77 ((e.shiftKey || (e.keyCode < 48 || e.keyCode > 57)) && (e.keyCode < 96 || e.keyCode > 105)) || 78 ((inputValue.length > maxCharacters - 1) && !selectAll)) 79 { 80 e.preventDefault(); 81 } 82 selectAll = false; 83 } 84 }); 85}; 86 87 88$(function() 89{ 90 // handling keyboard input for hero form. 91 var $input = $('.home-hero-form').find('input'); 92 smartasset.home.restrictNumberInput($input, 5); 93 94 // handling event tracking for the state and city links via JS 95 $('.js-us-region-links-list').on('click', 'a', function() 96 { 97 smartasset.analytics.eventTrack('home-statedirectory-click', {state: $(this).text()}); 98 }); 99 100 $('.js-top-cities-list').on('click', 'a', function() 101 { 102 smartasset.analytics.eventTrack('home-citydirectory-click', {city: $(this).data('cityName')}); 103 }); 104}); 105/** 106 * Port of SA's mobile navigation, this is scaled down as there is only one 107 * level of nesting required for the meganav. 108 */ 109 110$(function() 111{ 112 // Initialize the navigation menu. 113 (function initializeMenu() { 114 115 var $headerButtons = $('#header .buttons'),
116 $navCategories = $headerButtons.find('.nav-categories'), 117 $body = $(document.body), 118 isAnimating = false, 119 currentYPosition = null; 120 121 // Get all the main category links after the mobile sections have been appended. 122 var $navCategoryLinks = $navCategories.find('> ul > li.has-sub:not(.nav-more) > a:not(.js-static-link)'); 123 124 // Mobile: Append mobile category titles to the DOM, using the regular link titles. 125 $navCategoryLinks.each(function() { 126 var linkText = $(this).text(); 127 if (linkText) 128 { 129 $(this).parent().prepend("<h4 class=\"mobile-menu-category-title js-hidden\">" + linkText + "</h4>"); 130 } 131 }); 132 133 // Mobile: Navigate to a sub-category on clicking a main category link. 134 $navCategoryLinks.click(function(e) { 135 136 // Since these links can be clicked on desktop, check if on tablet size and below. 137 // NOTE: There is no screen width check in the other click event listeners, because those elements should only appear on mobile/tablet. 138 if ($(document).width() <= 1120) 139 { 140 // Do not not go to the default link URL. 141 e.preventDefault(); 142 143 if (isAnimating) return; 144 isAnimating = true; 145 146 var $this = $(this); 147 148 $navCategories.removeClass('overflowed'); 149 $navCategories.children('ul').toggleClass('offscreen'); 150 151 $this.siblings('.nav-sub-container').addClass('mobile-category-active overflowed'); 152 $this.siblings('.mobile-menu-category-title').removeClass('js-hidden'); 153 154 setTimeout(function () { 155 isAnimating = false; 156 }, 500); 157 } 158 }); 159 160 // Mobile: Navigate back to main categories on clicking the top titles in the sub menus. 161 $navCategories.find('.mobile-menu-category-title').click(function() { 162 if (!isAnimating) 163 { 164 var $this = $(this); 165 166 isAnimating = true; 167 168 $navCategories.children('ul').removeClass('offscreen'); 169 $navCategories.addClass('overflowed'); 170 171 setTimeout (function() { 172 $this.siblings('.nav-sub-container').removeClass('mobile-category-active'); 173 $this.addClass('js-hidden'); 174 isAnimating = false; 175 }, 500); 176 } 177 }); 178 179 // Mobile: Toggle the mobile menu on clicking the hamburger button, the mobile close button, or the mobile overlay (area outside the menu). 180 $('.header-mobile-menu-button, .mobile-menu-close-button, #mobile-menu-overlay').on('click', function () { 181 182 // Don't do anything if animating. 183 if (isAnimating) return; 184 185 var menuIsActive = $headerButtons.hasClass('mobile-menu-active'); 186 187 $('#mobile-menu-overlay').toggle(); 188 isAnimating = true; 189 190 // If menu is open, close it. 191 if (menuIsActive) 192 { 193 $headerButtons.removeClass('mobile-menu-active'); 194 195 // Let the body scroll. 196 window.requestAnimationFrame(function() 197 { 198 // Add a 'mobile-menu-open' class to the body to make it non-scrollable. 199 $body.removeClass('mobile-menu-open') 200 .css({ 201 top: '' 202 }); 203 204 window.scrollTo({ 205 top: currentYPosition, 206 behavior: 'instant' 207 }); 208 }); 209 210 setTimeout(function() { 211 // Reset elements after the menu has finished closing. 212 $headerButtons.removeClass('mobile-menu-visible'); 213 $navCategories.children('ul').removeClass('offscreen'); 214 $navCategories.find('.nav-sub-container').removeClass('mobile-category-active'); 215 }, 500); 216 } 217 else 218 { 219 // Toggle the visibility of the mobile menu, then scroll it into view. 220 $headerButtons.addClass('mobile-menu-visible'); 221 setTimeout(function() { 222 $headerButtons.addClass('mobile-menu-active'); 223 }, 10); 224 225 window.requestAnimationFrame(function() 226 { 227 // recording the current window position 228 currentYPosition = window.scrollY; 229 // Add a 'mobile-menu-open' class to the body to make it non-scrollable. 230 $body.addClass('mobile-menu-open') 231 .css({ 232 top: currentYPosition * -1 + 'px' 233 }); 234 }); 235 236 // Making the menu scrollable since this class was possibly removed. 237 $navCategories.addClass('overflowed'); 238 } 239 240 setTimeout(function() { 241 isAnimating = false;
242 }, 500); 243 }); 244 })(); 245}); 246// All tracking related functionality is contained here 247// mostly ported from SA. tweaked here and there 248var smartasset = smartasset || {}; 249 250smartasset.analytics = smartasset.analytics || {}; 251 252/** 253 * Initialize mixpanel link tracking 254 */ 255smartasset.analytics.initializeTracking = function() 256{ 257 // track the page view 258 smartasset.analytics.trackPageView(true); 259 260 // track the page view in facebook 261 smartasset.analytics.facebookTrack('ViewContent'); 262}; 263 264/** 265 * Track page view on MixPanel and GA 266 * @param noReferer -- boolean. Set to TRUE if you do not want to override the referer 267 */ 268smartasset.analytics.trackPageView = function(noReferer) 269{ 270 if ($.type(noReferer) !== "boolean") 271 { 272 noReferer = false; 273 } 274 275 if (IS_DEV) 276 { 277 console.log("Tracking page-view // noReferer: " + noReferer); 278 } 279 280 // send Google "pageview", a custom Google event 281 if (typeof ga === 'function') 282 { 283 if (!noReferer) 284 { 285 var url = prefixUrl(smartasset.page.originalUrl); 286 ga('set', 'referrer', url); 287 } 288 289 // "pageview", "page", and "title" are Google Analytics keywords 290 ga('send', 'pageview', { 291 'page': window.location.pathname, 292 'title': document.title 293 }); 294 295 if (IS_DEV) 296 { 297 console.log('PageView Track: ', window.location.pathname); 298 } 299 300 // Broadcast our "Page Loaded" event to all services 301 smartasset.analytics.track('Page Loaded'); 302 } 303}; 304 305/** 306 * Facebook tracking 307 * @param label: custom id to send to FB for tracking 308 * @param customData: a custom object sent with specifications about the event 309 */ 310 311smartasset.analytics.facebookTrack = function(label, customData) 312{ 313 // First check if the facebook tracker is instantiated 314 if (typeof fbq === 'function' && $.type(label) === 'string') 315 { 316 // Set the event type 317 var eventType; 318 if (label === 'ViewContent') 319 { 320 eventType = 'track'; 321 } 322 else 323 { 324 eventType = 'trackCustom'; 325 } 326 327 // Check if there is custom data 328 if ($.type(customData) !== 'object') 329 { 330 customData = {}; 331 } 332 333 // Include UTM Campaign params 334 var queryParams = $.queryParams(); 335 336 customData.utmsource = queryParams.utm_source ? queryParams.utm_source : undefined; 337 customData.utmcampaign = queryParams.utm_campaign ? queryParams.utm_campaign : undefined; 338 customData.utmmedium = queryParams.utm_medium ? queryParams.utm_medium : undefined; 339 customData.utmcontent = queryParams.utm_content ? queryParams.utm_content : undefined; 340 customData.utmterm = queryParams.utm_term ? queryParams.utm_term : undefined; 341 342 // Track the event 343 fbq(eventType, label, customData); 344 345 if (IS_DEV) { 346 console.log('Facebook Track: ' + label, customData); 347 } 348 } 349}; 350 351/** 352 * Event Tracker 353 * @param mpEvent: name of the MP event 354 * @param mpParams: additional optional items to include in the MP event 355 */ 356smartasset.analytics.eventTrack = function(mpEvent, mpParams) 357{ 358 if ($.type(mpEvent) === 'string') 359 { 360 // Track in Google Analytics (GA) and Mixpanel (MP) 361 if ($.type(mpParams) !== 'object') 362 { 363 mpParams = {}; 364 } 365 366 smartasset.analytics.track(mpEvent, mpParams); 367 } 368}; 369 370/** 371 * PLEASE READ NOTES IN THE CODE ON HOW TO USE THIS! 372 * track("eventName", { value: 4, color: "Blue"}) 373 * 374 * @param {string} eventKey 375 * @param {Object=} data optional object with parameters for the event. 376 * @param {boolean} skipMixpanel if true, skips sending data to Mixpanel 377 */ 378smartasset.analytics.track = function(eventKey, data, skipMixpanel) 379{ 380 var skipGoogleAnalytics = false, 381 eventsToSkipInGA = ['Page Loaded']; 382 383 if (eventsToSkipInGA.indexOf(eventKey) !== -1) 384 { 385 skipGoogleAnalytics = true; 386 } 387 388 // Omit events from MP 389 if (typeof skipMixpanel !== 'boolean') 390 { 391 skipMixpanel = false;
392 } 393 394 if (IS_DEV) 395 { 396 console.log('Tracking Event: ', eventKey, data); 397 } 398 399 // instantiate a copy of the data parameter that we can mutate 400 var properties = (typeof data === 'object') ? data : {}; 401 402 // avoid logging an invalid key 403 if (!eventKey || typeof eventKey !== 'string') 404 { 405 console.error('Error - key is invalid'); 406 return; 407 } 408 409 properties.page = window.location.pathname; 410 411 // Raise an event within the window. This is useful for detecting 412 // events in other code. 413 // NOTE: replace "createEvent" with CustomEvent when we deprecate IE8 414 if (document.createEvent && document.dispatchEvent) 415 { 416 var evt = document.createEvent('Event'); 417 evt.initEvent(eventKey, true, true); 418 document.dispatchEvent(evt); 419 } 420 421 // Send event to Mixpanel 422 if (!skipMixpanel && typeof mixpanel === "object" && typeof mixpanel.track === 'function') 423 { 424 mixpanel.track(eventKey, properties); 425 } 426 427 // Send event to Google Analytics 428 if (!skipGoogleAnalytics && typeof ga === 'function') 429 { 430 ga('send', 'event', eventKey, eventKey, properties); 431 } 432}; 433
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.