PageSourceSearch

https://freespray.com/files/template/template.js

js freespray.com collected 2026-10-04 13:23:09 UTC 23,556 bytes, 659 lines download raw bytes

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.