PageSourceSearch

https://alphalockandsafe.com/js/address-autocomplete.js?v=20260519

js alphalockandsafe.com collected 2026-10-04 19:14:07 UTC 6,010 bytes, 149 lines download raw bytes

1// Google Places address autocomplete for the booking forms.
2//
3// USAGE
4// -----
5// 1. Add an attribute to address inputs:
6//      data-address-autocomplete="full"   single-field (street + city + zip combined)
7//      data-address-autocomplete="street" street-only; sibling fields can be:
8//         data-address-component="city"
9//         data-address-component="zip"
10//         data-address-component="state"
11// 2. Set the Google Places API key once, before this script runs:
12//      <script>window.ALPHA_GMAPS_KEY = "YOUR_KEY";</script>
13//    or on the script tag itself:
14//      <script src="/js/address-autocomplete.js" data-google-places-key="YOUR_KEY" defer></script>
15//
16// If no key is provided the script no-ops silently and the browser's native
17// autocomplete (`autocomplete="street-address"`) continues to work.
18//
19// The autocomplete is biased to Westchester County, NY but does not restrict
20// — out-of-area customers can still type a full address.
21
22(function () {
23  var inputs = document.querySelectorAll('[data-address-autocomplete]');
24  if (!inputs.length) return;
25
26  function getApiKey() {
27    if (window.ALPHA_GMAPS_KEY) return window.ALPHA_GMAPS_KEY;
28    var scripts = document.querySelectorAll('script[data-google-places-key]');
29    for (var i = 0; i < scripts.length; i++) {
30      var k = scripts[i].getAttribute('data-google-places-key');
31      if (k) return k;
32    }
33    return null;
34  }
35
36  var apiKey = getApiKey();
37  if (!apiKey) return;
38
39  function loadGoogleMaps(key) {
40    if (window.google && window.google.maps && window.google.maps.places) {
41      return Promise.resolve();
42    }
43    var existing = document.getElementById('alpha-google-maps-script');
44    if (existing) {
45      return new Promise(function (resolve, reject) {
46        existing.addEventListener('load', resolve, { once: true });
47        existing.addEventListener('error', reject, { once: true });
48      });
49    }
50    return new Promise(function (resolve, reject) {
51      var s = document.createElement('script');
52      s.id = 'alpha-google-maps-script';
53      s.src = 'https://maps.googleapis.com/maps/api/js?key=' + encodeURIComponent(key) + '&libraries=places&loading=async&v=weekly';
54      s.async = true;
55      s.defer = true;
56      s.onload = resolve;
57      s.onerror = reject;
58      document.head.appendChild(s);
59    });
60  }
61
62  function findSibling(input, type) {
63    var form = input.form || input.closest('form');
64    if (!form) return null;
65    return form.querySelector('[data-address-component="' + type + '"]');
66  }
67
68  function setVal(el, val) {
69    if (!el || val == null) return;
70    el.value = val;
71    el.dispatchEvent(new Event('input', { bubbles: true }));
72    el.dispatchEvent(new Event('change', { bubbles: true }));
73    // If the booking widget marked this field invalid, clear that now.
74    el.removeAttribute('aria-invalid');
75  }
76
77  function extractAddressParts(place) {
78    var out = { street_number: '', route: '', city: '', state: '', zip: '', country: '' };
79    if (!place.address_components) return out;
80    place.address_components.forEach(function (c) {
81      var t = c.types || [];
82      if (t.indexOf('street_number') > -1) out.street_number = c.long_name;
83      else if (t.indexOf('route') > -1) out.route = c.long_name;
84      else if (t.indexOf('locality') > -1) out.city = c.long_name;
85      else if (t.indexOf('sublocality_level_1') > -1 && !out.city) out.city = c.long_name;
86      else if (t.indexOf('sublocality') > -1 && !out.city) out.city = c.long_name;
87      else if (t.indexOf('postal_town') > -1 && !out.city) out.city = c.long_name;
88      else if (t.indexOf('administrative_area_level_1') > -1) out.state = c.short_name;
89      else if (t.indexOf('postal_code') > -1) out.zip = c.long_name;
90      else if (t.indexOf('country') > -1) out.country = c.short_name;
91    });
92    return out;
93  }
94
95  function attach(input, bounds) {
96    var mode = input.getAttribute('data-address-autocomplete'); // "full" | "street"
97    var options = {
98      componentRestrictions: { country: 'us' },
99      fields: ['formatted_address', 'address_components'],
100      types: ['address']
101    };
102    if (bounds) {
103      options.bounds = bounds;
104      // Don't strictly clip — bias only. Users outside Westchester still work.
105      options.strictBounds = false;
106    }
107
108    var ac = new google.maps.places.Autocomplete(input, options);
109
110    ac.addListener('place_changed', function () {
111      var place = ac.getPlace();
112      if (!place || !place.address_components) return;
113      var parts = extractAddressParts(place);
114      var streetLine = (parts.street_number + ' ' + parts.route).trim();
115
116      if (mode === 'full') {
117        setVal(input, place.formatted_address ||
118          [streetLine, parts.city, (parts.state + ' ' + parts.zip).trim()].filter(Boolean).join(', '));
119      } else {
120        setVal(input, streetLine || place.formatted_address || '');
121        setVal(findSibling(input, 'city'), parts.city);
122        setVal(findSibling(input, 'zip'), parts.zip);
123        setVal(findSibling(input, 'state'), parts.state);
124      }
125    });
126
127    // Stop Enter from submitting the form while picking a suggestion.
128    input.addEventListener('keydown', function (e) {
129      if (e.key !== 'Enter') return;
130      var dropdown = document.querySelector('.pac-container');
131      if (dropdown && dropdown.offsetParent !== null) e.preventDefault();
132    });
133  }
134
135  loadGoogleMaps(apiKey).then(function () {
136    var bounds = null;
137    if (window.google && window.google.maps && window.google.maps.LatLngBounds) {
138      bounds = new google.maps.LatLngBounds(
139        new google.maps.LatLng(40.85, -73.95), // SW corner – just south of Yonkers
140        new google.maps.LatLng(41.40, -73.45)  // NE corner – just north of Peekskill
141      );
142    }
143    Array.prototype.forEach.call(inputs, function (input) {
144      try { attach(input, bounds); } catch (_) { /* fall back to native */ }
145    });
146  }).catch(function () {
147    // Maps SDK failed to load – leave native autocomplete in place.
148  });
149})();

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.