1// FILE: TEMPLATE.JS 2$(function () { 3 4 // run content functions 5 InitContent(); 6 7 // scroll to top button 8 $('.nav-toggle').on('click', toggleNav); 9 $('.has-submenu').on('click', toggleNavSubmenu); 10 $('.quote').on('click', showQuoteModal); 11 $('.jobs').on('click', showCareersModal); 12 $('#modals-background, .close-modal').on('click', toggleModals); 13 $('.scrolltop-button').on('click', scrollToTop); 14 15 // run resize functions 16 $(window).on('resize', function(){ 17 SetYouTubeVideos(); 18 resetNav(); 19 }); 20 21 // run scroll functions 22 $(window).on('scroll', function(){ 23 toggleBodyScroll(); 24 }); 25 26 // toggle input label animation 27 $('input, textarea').on('focusin', function() { 28 $(this).parent().find('label').addClass('active'); 29 }); 30 31 $('input, textarea').on('focusout', function() { 32 if (!this.value) { 33 $(this).parent().find('label').removeClass('active'); 34 } 35 }); 36 37}); 38 39// runs all the content functions 40function InitContent(){ 41 runStartup(); 42 resetNav(); 43 setActivePage(); 44 AssignBackgrounds(); 45 RemoveEmptySections(); 46 SetMissingAltText(); 47 SetYouTubeVideos(); 48} 49 50// set initial assignments 51function runStartup(){ 52 $('.form-lawnline input, .form-lawnline textarea').attr('required', true); 53 $('.popup-default, .preloader-default').remove(); 54 $('#nav .logo').parent('li').addClass('has-logo'); 55 //$('#nav .show-mobile').parent('li').addClass('only-mobile'); 56 //$('#nav .is-menu').closest('li').addClass('has-menu'); 57 //$('#nav .is-submenu').parent('li').addClass('has-submenu'); 58 //$('#nav .is-deepmenu').parent('li').addClass('has-deepmenu'); 59 // new assignments 60 $('#nav .inner>ul>li>ul').parent('li').addClass('has-submenu'); 61 // standard assignments 62 $('#nav .mega').closest('li').addClass('has-submenu has-mega'); 63 $('#nav ul svg').parent('a').addClass('has-icon'); 64 //$('#nav .nav-callout').parent('li').addClass('has-callout'); 65 $('#nav nav .inner>ul>li>a[href*="service-areas"]').closest('li').addClass('is-areas-menu'); 66 $('input[type=button], button').addClass('button'); 67 $('.button').parents('p, li').addClass('has-button'); 68 //$('.packages').prev('header').addClass('has-packages'); 69 //$('.packages .ribbon').closest('.box').addClass('has-ribbon'); 70 $('.is-home').parent('header').addClass('header-home'); 71 setTimeout(function() { 72 $('#nav, #modals, header').removeClass('is-hidden'); 73 }, 500); 74} 75 76// toggle is-scrolled class on body 77function toggleBodyScroll(){ 78 if($(this).scrollTop() > 1){ 79 $('body').addClass('is-scrolled'); 80 } 81 else{ 82 $('body').removeClass('is-scrolled'); 83 } 84} 85 86// set active page in menus 87function setActivePage() { 88 89 // get the relative path 90 var relative_path = window.location.pathname.toLowerCase(); 91 92 // find menu items 93 var menu_items = $('ul.is-menu').children('li'); 94 95 // remove active class from all 96 $(menu_items).find('a').removeClass('is-active'); 97 98 // loop menut items 99 for (var i = 0; i < menu_items.length; i++) { 100 101 // set item on loop 102 var item = menu_items[i]; 103 104 // get the link to this item 105 var item_link = $(item).find('a').first(); 106 if (!jsUtils.IsNullOrEmpty(item_link)) { 107 108 // get the url and remove any hashtags 109 var item_url = $(item_link).prop('href').toLowerCase(); 110 if (jsUtils.Contains(item_url, '#')) 111 item_url = item_url.substr(0, item_url.indexOf('#')); 112 113 // fix home 114 if (jsUtils.Contains(item_url, '/home/')) 115 item_url = '/'; 116 117 // build the string to replace the domain in the item_url 118 item_url = item_url.replace(window.location.hostname.toLowerCase(), ''); 119 if (jsUtils.StartsWith(item_url, 'http://')) 120 item_url = item_url.replace('http://', ''); 121 if (jsUtils.StartsWith(item_url, 'https://')) 122 item_url = item_url.replace('https://', ''); 123 124 // check if the relative path is inside the blog 125 if (jsUtils.Contains(relative_path, '/blog/')) 126 relative_path = '/blog/'; 127 128 // check if the item_url links to the current url 129 if (relative_path === item_url) 130 $(item_link).addClass('is-active'); 131 } 132 } 133} 134 135// set background images
136function AssignBackgrounds(){ 137 138 // get background images 139 var background_images = $('img.background'); 140 141 // sets the background image 142 $(background_images).each(function(){ 143 var bg_blocks = $(this).parents('header, main, footer, section, div, a').first(); 144 var bg_src = $(this).prop('src'); 145 146 // set objects that can have background images / set image as background image css / add class bg-image 147 bg_blocks.css('background-image', 'url(' + bg_src + ')').addClass('background-image'); 148 149 // option for background-attachment:fixed 150 if($(this).hasClass('fixed')){ 151 bg_blocks.addClass('background-fixed'); 152 } 153 154 // option for background-position:top 155 if($(this).hasClass('top')){ 156 bg_blocks.addClass('background-top'); 157 } 158 159 // option for background-position:bottom 160 if($(this).hasClass('bottom')){ 161 bg_blocks.addClass('background-bottom'); 162 } 163 164 // option for turning overlay off 165 if($(this).hasClass('no-overlay')){ 166 bg_blocks.addClass('overlay-off'); 167 } 168 169 // remove img when complete 170 $(this).remove(); 171 172 }); 173 174} 175 176// auto resize for youtube embeds 177function SetYouTubeVideos(){ 178 // get all youtube videos 179 var Videos = $('iframe[src*="youtube.com"]'); 180 $(Videos).each(function(){ 181 var $vid = $(this); 182 if(jsUtils.IsNullOrEmpty($vid.data('originalwidth'))) 183 { 184 $vid.data('originalwidth', $vid.prop('width')); 185 $vid.data('originalheight', $vid.prop('height')); 186 } 187 188 var ratio = parseFloat($vid.data('originalwidth') / $vid.data('originalheight')); 189 if(String($vid.prop('width')) != '100%') 190 $vid.width('100%'); 191 if($vid.width() > 1000) 192 $vid.width(1000); 193 $vid.height($vid.width() / ratio); 194 }); 195} 196 197// Sets correct title casing 198function toTitleCase(str) { 199 return str.replace( 200 /\w\S*/g, 201 function (txt) { 202 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); 203 } 204 ); 205} 206 207// Sets missing alt text attributes on images 208function SetMissingAltText() { 209 210 // set alt text 211 $('img').each(function () { 212 var AltText = $(this).attr('alt'); 213 if (typeof AltText === 'undefined' || AltText.length < 1) { 214 // set the alt text to the file name 215 AltText = $(this).attr('src'); 216 $(this).attr('alt', AltText); 217 218 // cut the alt down to the file name 219 if (jsUtils.Contains(AltText, '/')) 220 AltText = AltText.substring(AltText.lastIndexOf('/') + 1); 221 if (jsUtils.Contains(AltText, '.')) 222 AltText = AltText.substring(0, AltText.indexOf('.')); 223 224 // format 225 AltText = jsUtils.ReplaceAll(AltText, '-', ' '); 226 AltText = jsUtils.ReplaceAll(AltText, 'gallery ', ''); 227 AltText = jsUtils.ReplaceAll(AltText, 'icon ', ''); 228 AltText = jsUtils.ReplaceAll(AltText, 'logo ', ''); 229 AltText = jsUtils.ReplaceAll(AltText, 'content ', ''); 230 AltText = toTitleCase(AltText); 231 $(this).attr('alt', AltText); 232 } 233 }); 234} 235 236// removes the empty <section> tags, mostly caused by unused ContentPlaceholders in the master page. 237function RemoveEmptySections(){ 238 239 // remove empty content sections 240 $('.remove-empty').each(function () { 241 var ContainersToCheck = $(this).find('.check-empty'); 242 if(jsUtils.IsNullOrEmpty(ContainersToCheck)) 243 ContainersToCheck = [this]; 244 245 // loop each container to check if empty 246 var Remove = true; 247 $(ContainersToCheck).each(function(){ 248 //console.log('=------------------------------\r\n\r\nHTML:' + $(this).html().trim()); 249 250 // check if we're still planning to remove 251 if(Remove === true) 252 { 253 // check if there is content 254 var HasContent = $(this).html().trim().length > 0; 255 256 // check if the only content is a tobject (front end admin highlighter) 257 if(HasContent === true && $(this).children().length == 1 && $(this).children('tobject').length > 0) 258 { 259 if($(this).children().first().html().trim().length < 1) 260 HasContent = false; 261 } 262 263 // check if we need to remove 264 if(HasContent === true) 265 Remove = false; 266 } 267 }); 268 269 // check if we need to remove 270 if(Remove === true) 271 $(this).remove(); 272 }); 273} 274 275// Scrolling 276 277// Scroll to top 278function scrollToTop(){ 279 $("html, body").animate({ scrollTop: 0 }, 500); 280 return false; 281} 282 283// smooth scroll to identifier 284$("a[href^='#']").click(function(e) { 285 e.preventDefault(); 286 287 var position = $($(this).attr("href")).offset().top - 132; 288 289 $("body, html").animate({ 290 scrollTop: position 291 }, 500, 'linear' ); 292}); 293 294// Navigation 295function toggleNav(){ 296 if(!$('#nav nav').hasClass('is-active')){ 297 openNav(); 298 } 299 else{ 300 closeNav(); 301 } 302} 303 304function openNav(){ 305 $('.nav-toggle, #nav nav').addClass('is-active'); 306} 307 308// closes navigation 309function closeNav(){ 310 $('.nav-toggle, #nav nav').removeClass('is-active'); 311 removeNavActive(); 312} 313 314function removeNavActive(){ 315 $('#nav nav>.inner>ul>li.has-submenu').removeClass('is-active'); 316} 317 318// toggles second tier navigation items 319function toggleNavSubmenu(){ 320 var win_width = document.body.clientWidth; 321 if(win_width >= 768){ 322 removeNavActive(); 323 } 324 if(win_width <= 767){ 325 if(!$(this).hasClass('is-active')){ 326 $(this).addClass('is-active'); 327 } 328 else{ 329 $(this).removeClass('is-active'); 330 } 331 } 332} 333 334// correct mobile nav at viewport height 335function resetNav(){ 336 var window_width = document.body.clientWidth; 337 if(window_width >= 768){ 338 closeNav(); 339 $('#nav nav>.inner>ul>li.has-submenu>a').each(function(){ 340 if($(this).attr('href') == 'javascript:void(0)' && !jsUtils.IsNullOrEmpty($(this).data('href'))) 341 $(this).attr('href', $(this).data('href')); 342 });
343 } 344 if(window_width <= 767){ 345 $('#nav nav>.inner>ul>li.has-submenu>a').each(function(){ 346 $(this).data('href', $(this).attr('href')); 347 $(this).attr('href', 'javascript:void(0)'); 348 }); 349 } 350} 351 352// Modals 353function toggleModals(){ 354 if(!$('#modals').hasClass('is-active')){ 355 clearModals(); 356 $('body').addClass('no-scroll'); 357 $('#modals').addClass('is-active'); 358 } 359 else{ 360 clearModals(); 361 $('body').removeClass('no-scroll'); 362 $('#modals').removeClass('is-active'); 363 } 364} 365 366function clearModals(){ 367 $('.modal').removeClass('is-active'); 368} 369 370function showQuoteModal(){ 371 clearModals(); 372 toggleModals(); 373 $('#modal-quote').addClass('is-active'); 374} 375 376function showCareersModal(){ 377 clearModals(); 378 toggleModals(); 379 $('#modal-careers').addClass('is-active'); 380} 381 382// global handler for lawnline form submissions to send the data to whatconverts. 383$(function() { 384 385 // find all the forms and sets them up for whatconverts 386 $('form.form-lawnline').each(function(){ 387 388 // check if there are any options missing values 389 $(this).find('option:not([value])').each(function(){ 390 391 // set the value to the text 392 $(this).attr('value', $(this).text()); 393 }); 394 395 // append a button for whatconverts 396 $(this).append('<input type="button" class="btnWCSubmit" style="display: none;" />'); 397 }); 398 399 // global response for lawnline native form submissions 400 jsCore.OnSubmitForm(function(objResponse) { 401 402 // check if successful 403 if (objResponse.ResponseCode == -1) { 404 405 // find the form wrapper 406 var $objForm = $(objResponse.CustomData.Event.target).parents('form.form-lawnline').first(); 407 408 // make sure exists 409 if(!jsUtils.IsNullOrEmpty($objForm)){ 410 411 // click the button 412 $objForm.find('.btnWCSubmit').first().click(); 413 414 // give whatconverts a few seconds to process, then redirect to the thank you page 415 setTimeout(function(){ 416 window.location.href = '/thank-you/'; 417 }, 3000); 418 } 419 } 420 }); 421}); 422 423// Integration of address autocomplete from the Google Maps API 424// The Google Maps API reference MUST load before this code executes. 425var ACInstances = []; 426$(function () { 427 428 // parameters that can be altered 429 var GoogleMapsAPIKey = 'AIzaSyDxF4fu_hSbaQAU4eALKUvxEPkg15Td5Gg'; 430 var CallbackFunction = 'InitAutoComplete'; 431 432 // load the Google Maps API javascript reference 433 var GMapsApiScript = document.createElement('script'); 434 GMapsApiScript.src = 'https://maps.googleapis.com/maps/api/js?key=' + GoogleMapsAPIKey + '&callback=' + CallbackFunction + '&libraries=places'; 435 document.body.appendChild(GMapsApiScript); 436 437 // load the required css changes for modals 438 var GMapsStyle = document.createElement('style'); 439 GMapsStyle.innerHTML = '.pac-container { z-index: 9999999 !important; }'; 440 document.head.appendChild(GMapsStyle); 441}); 442 443// Executes upon the loading of the Google Maps API reference. 444function InitAutoComplete() { 445 446 // load autocomplete on any lawnline forms that exist 447 pvt_ACLawnlineForms(); 448 449 // load autocomplete on any other forms that exist 450 pvt_ACOtherForms(); 451 452 // load autocomplete on any custom forms that the system didn't pick up in pvt_ACOtherForms 453 // duplicate the code below for each form, set the values, uncomment. 454 // you can pass null to any fields that don't exist 455 //InitAutoCompleteInstance( 456 // $('address_field_selector'), 457 // $('city_field_selector'), 458 // $('state_field_selector'), 459 // $('zipcode_field_selector') 460 //); 461 462 // example using Serivce Autopilot form 463 //InitAutoCompleteInstance( 464 //$('input[name="xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_Address'), 465 //$('input[name="xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_City'), 466 //$('input[name="xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_State'), 467 //$('input[name="xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_Zip') 468 //); 469} 470 471// Loops the native Lawnline forms and activates the autocomplete 472function pvt_ACLawnlineForms() { 473 474 // handle any native Lawnline forms 475 $('input[data-contactfield="address"]').each(function () { 476 477 // write the address field on the loop to a local variable 478 var $objAddressField = $(this); 479 480 // traverse up the tree until we find the parent container around the form. 481 // since lawnline forms don't need to use an actual form, we will look for the submit button
482 // once the submit button is found, we know we're on the overall wrapper 483 var $divFormWrapper = null; 484 var TraverseIndex = 0; 485 while ($divFormWrapper === null) { 486 var $divParent = $objAddressField.parent(); 487 for (var i = 0; i < TraverseIndex; i++) { 488 $divParent = $divParent.parent(); 489 } 490 if (!jsUtils.IsNullOrEmpty($divParent.find('[data-action="form-submit"]'))) 491 $divFormWrapper = $divParent; 492 TraverseIndex++; 493 if (TraverseIndex > 10) 494 break; 495 } 496 497 // check if the form wrapper was found 498 if (!jsUtils.IsNullOrEmpty($divFormWrapper)) { 499 500 // find the other fields 501 var $objCityField = $divFormWrapper.find('[data-contactfield="city"]'); 502 var $objStateField = $divFormWrapper.find('[data-contactfield="state"]'); 503 var $objZipCodeField = $divFormWrapper.find('[data-contactfield="postalcode"]'); 504 505 // load with all fields 506 InitAutoCompleteInstance($objAddressField, $objCityField, $objStateField, $objZipCodeField); 507 } else { 508 509 // only load the address field, other fields were not found 510 InitAutoCompleteInstance($objAddressField); 511 } 512 }); 513} 514 515function pvt_ACOtherForms() { 516 517 // loop all form elements 518 $('form').each(function () { 519 520 // write the form to an object 521 var $form = $(this); 522 523 // try to find the address field (if one exists) 524 var $txtAddress = $form.find('input[type="text"]').filter(function () { 525 if (jsUtils.IsNullOrEmpty($(this).attr('placeholder'))) 526 return false; 527 return jsUtils.Contains($(this).attr('placeholder').toLowerCase(), 'address') && !jsUtils.Contains($(this).attr('placeholder').toLowerCase(), 'email'); 528 }).first(); 529 if (jsUtils.IsNullOrEmpty($txtAddress)) { 530 var $lblAddress = $form.find('label').filter(function () { 531 return jsUtils.Contains($(this).text().toLowerCase(), 'address') && !jsUtils.Contains($(this).text().toLowerCase(), 'email'); 532 }).first(); 533 if (!jsUtils.IsNullOrEmpty($lblAddress) && !jsUtils.IsNullOrEmpty($lblAddress.attr('for'))) 534 $txtAddress = $('#' + $lblAddress.attr('for')); 535 else 536 return; 537 } 538 539 // try to find the city field (if one exists) 540 var $txtCity = $form.find('input[type="text"]').filter(function () { 541 if (jsUtils.IsNullOrEmpty($(this).attr('placeholder'))) 542 return false; 543 return jsUtils.Contains($(this).attr('placeholder').toLowerCase(), 'city'); 544 }).first(); 545 if (jsUtils.IsNullOrEmpty($txtCity)) { 546 var $lblCity = $form.find('label').filter(function () { 547 return jsUtils.Contains($(this).text().toLowerCase(), 'city'); 548 }).first(); 549 if (!jsUtils.IsNullOrEmpty($lblCity) && !jsUtils.IsNullOrEmpty($lblCity.attr('for'))) 550 $txtCity = $('#' + $lblCity.attr('for')); 551 } 552 553 // try to find the state field (if one exists) 554 var $txtState = $form.find('input[type="text"]').filter(function () { 555 if (jsUtils.IsNullOrEmpty($(this).attr('placeholder'))) 556 return false; 557 return jsUtils.Contains($(this).attr('placeholder').toLowerCase(), 'state'); 558 }).first(); 559 if (jsUtils.IsNullOrEmpty($txtState)) { 560 var $lblState = $form.find('label').filter(function () { 561 return jsUtils.Contains($(this).text().toLowerCase(), 'state'); 562 }).first(); 563 if (!jsUtils.IsNullOrEmpty($lblState) && !jsUtils.IsNullOrEmpty($lblState.attr('for'))) 564 $txtState = $('#' + $lblState.attr('for')); 565 } 566 567 // try to find the zip/postal code field (if one exists) 568 var $txtZip = $form.find('input[type="text"]').filter(function () { 569 if (jsUtils.IsNullOrEmpty($(this).attr('placeholder'))) 570 return false; 571 return jsUtils.Contains($(this).attr('placeholder').toLowerCase(), 'zip') || jsUtils.Contains($(this).attr('placeholder').toLowerCase(), 'postal'); 572 }).first(); 573 if (jsUtils.IsNullOrEmpty($txtZip)) { 574 var $lblZip = $form.find('label').filter(function () { 575 return jsUtils.Contains($(this).text().toLowerCase(), 'zip') || jsUtils.Contains($(this).text().toLowerCase(), 'postal'); 576 }).first(); 577 if (!jsUtils.IsNullOrEmpty($lblZip) && !jsUtils.IsNullOrEmpty($lblZip.attr('for')))
578 $txtZip = $('#' + $lblZip.attr('for')); 579 } 580 581 // load the autocomplete 582 InitAutoCompleteInstance($txtAddress, $txtCity, $txtState, $txtZip); 583 }); 584} 585 586// Initializes an instance of the autocomplete from the Google API 587// Only objAddressField is required, all other fields are optional. 588// Each are the actual javascript or jquery objects, not a selector. 589// If other fields are supplied, the address parts will be populated to each of those fields. 590// If other fields are not supplied, the entire address will be populated to the address field. 591function InitAutoCompleteInstance(objAddressField, objCityField, objStateField, objZipCodeField) { 592 593 // make sure this address hasn't already been activated 594 if ($.inArray(objAddressField, ACInstances) >= 0) 595 return; 596 597 // add to the list of activated autocompletes 598 ACInstances.push(objAddressField); 599 600 // set variable to hold the current placeholder value so we can replace it 601 var PlaceholderText = $(objAddressField).attr('placeholder'); 602 if (jsUtils.IsNullOrEmpty(PlaceholderText)) 603 PlaceholderText = ''; 604 605 // create the instance of the autocomplete on the address field 606 var autocomplete = new google.maps.places.Autocomplete($(objAddressField)[0], { 607 types: ['geocode'] 608 }); 609 610 // reset placeholder text 611 $(objAddressField).attr('placeholder', PlaceholderText); 612 613 // add a listener to the API when an address is selected 614 autocomplete.addListener('place_changed', function () { 615 616 // get the selected address 617 var place = autocomplete.getPlace(); 618 619 // write the address to a hidden div temporarily (easier to get the data from there) 620 $('body').append('<div id="tempAddress" style="display: none;">' + place.adr_address + '</div>'); 621 622 // extract the full address 623 var acAddress = $('#tempAddress .street-address').text(); 624 var acCity = $('#tempAddress .locality').text(); 625 var acState = $('#tempAddress .region').text(); 626 var acZipCode = $('#tempAddress .postal-code').text(); 627 628 // if the zip code returned the extended version, remove the 4 extended digits 629 if (jsUtils.Contains(acZipCode, '-')) 630 acZipCode = acZipCode.substr(0, acZipCode.indexOf('-')); 631 632 // check if we are only using the address field, or have the other fields too 633 if (!jsUtils.IsNullOrEmpty(objCityField) || !jsUtils.IsNullOrEmpty(objStateField) || !jsUtils.IsNullOrEmpty(objZipCodeField)) { 634 635 // write the value to the Address field 636 $(objAddressField).val(acAddress); 637 638 // check if we have the City field and write the value 639 if (!jsUtils.IsNullOrEmpty(objCityField)) 640 $(objCityField).val(acCity); 641 642 // check if we have the State field and write the value 643 if (!jsUtils.IsNullOrEmpty(objStateField)) 644 $(objStateField).val(acState); 645 646 // check if we have a zip code field and write the value 647 if (!jsUtils.IsNullOrEmpty(objZipCodeField)) 648 $(objZipCodeField).val(acZipCode); 649 650 } else { 651 652 // only the address field exists, write the full address to the one field 653 $(objAddressField).val(acAddress + ', ' + acCity + ', ' + acState + ' ' + acZipCode); 654 } 655 656 // remove the temp address holder 657 $('#tempAddress').remove(); 658 }); 659}
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.