PageSourceSearch

https://www.georgetowncoc.com/assets/controllers/google_address_autocomplete_controller-c07eded6.js

js georgetowncoc.com collected 2026-09-25 04:04:45 UTC 5,665 bytes, 153 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2import { loadGoogleMaps } from "lib/google_maps_loader"
3
4function findComponent(components, type) {
5  return components.find((c) => c.types?.includes(type))
6}
7
8function readLongText(component) {
9  // Places API (New) uses longText/shortText; legacy uses long_name/short_name.
10  return component?.longText || component?.long_name
11}
12
13function readShortText(component) {
14  return component?.shortText || component?.short_name
15}
16
17export default class extends Controller {
18  static values = { apiKey: String }
19  static targets = [
20    "widget",
21    "line1",
22    "line2",
23    "city",
24    "region",
25    "postalCode",
26    "country",
27    "formatted",
28    "placeId",
29    "latitude",
30    "longitude"
31  ]
32
33  connect() {
34    const apiKey = (this.apiKeyValue || "").trim()
35    if (!apiKey) return
36    if (!this.hasLine1Target) return
37
38    loadGoogleMaps(apiKey, ["places"])
39      .then(() => this._initPlaces())
40      .catch(() => {
41        // Autocomplete is progressive enhancement; keep the form usable without it.
42      })
43  }
44
45  _initPlaces() {
46    // Clear stale place metadata if a user manually edits address fields after selecting a place.
47    ;[
48      this.hasLine1Target ? this.line1Target : null,
49      this.hasLine2Target ? this.line2Target : null,
50      this.hasCityTarget ? this.cityTarget : null,
51      this.hasRegionTarget ? this.regionTarget : null,
52      this.hasPostalCodeTarget ? this.postalCodeTarget : null,
53      this.hasCountryTarget ? this.countryTarget : null
54    ]
55      .filter(Boolean)
56      .forEach((el) => el.addEventListener("input", () => this._clearPlaceMeta()))
57
58    // New customers should use PlaceAutocompleteElement (not legacy Autocomplete).
59    if (this.hasWidgetTarget) {
60      this._wirePlaceAutocompleteElement()
61      return
62    }
63
64    // Fallback (older integrations / views): legacy Autocomplete if available.
65    if (window.google?.maps?.places?.Autocomplete) this._wireLegacyAutocomplete()
66  }
67
68  _wireLegacyAutocomplete() {
69    this.autocomplete = new window.google.maps.places.Autocomplete(this.line1Target, {
70      fields: ["address_components", "geometry", "place_id", "formatted_address"],
71      types: ["address"]
72    })
73
74    this.autocomplete.addListener("place_changed", () => {
75      const place = this.autocomplete?.getPlace?.()
76      if (!place) return
77      this._applyPlaceLikeResult({
78        addressComponents: place.address_components,
79        formattedAddress: place.formatted_address,
80        placeId: place.place_id,
81        location: place.geometry?.location
82      })
83    })
84  }
85
86  _wirePlaceAutocompleteElement() {
87    const el = this.widgetTarget
88
89    el.addEventListener("gmp-select", async (event) => {
90      const placePrediction = event?.placePrediction || event?.detail?.placePrediction
91      if (!placePrediction?.toPlace) return
92
93      try {
94        const place = placePrediction.toPlace()
95        if (place?.fetchFields) {
96          await place.fetchFields({ fields: ["addressComponents", "formattedAddress", "location", "id"] })
97        }
98
99        this._applyPlaceLikeResult({
100          addressComponents: place.addressComponents,
101          formattedAddress: place.formattedAddress,
102          placeId: place.id || placePrediction.placeId,
103          location: place.location
104        })
105      } catch {
106        // Keep the form usable even if Places fetch fails.
107      }
108    })
109  }
110
111  _applyPlaceLikeResult({ addressComponents, formattedAddress, placeId, location }) {
112    const components = addressComponents || []
113
114    const streetNumber = readShortText(findComponent(components, "street_number"))
115    const route = readShortText(findComponent(components, "route"))
116    const city =
117      readLongText(findComponent(components, "locality")) ||
118      readLongText(findComponent(components, "postal_town")) ||
119      readLongText(findComponent(components, "sublocality_level_1"))
120    const region = readShortText(findComponent(components, "administrative_area_level_1"))
121    const postalCode = readShortText(findComponent(components, "postal_code"))
122    const country = readShortText(findComponent(components, "country"))
123
124    const line1 = [streetNumber, route].filter(Boolean).join(" ").trim()
125    if (this.hasLine1Target && line1) this.line1Target.value = line1
126
127    if (this.hasCityTarget && city) this.cityTarget.value = city
128    if (this.hasRegionTarget && region) this.regionTarget.value = region
129    if (this.hasPostalCodeTarget && postalCode) this.postalCodeTarget.value = postalCode
130    if (this.hasCountryTarget && country) this.countryTarget.value = country
131
132    if (this.hasFormattedTarget) this.formattedTarget.value = formattedAddress || ""
133    if (this.hasPlaceIdTarget) this.placeIdTarget.value = placeId || ""
134
135    if (location) {
136      const lat = typeof location.lat === "function" ? location.lat() : location.lat
137      const lng = typeof location.lng === "function" ? location.lng() : location.lng
138      if (this.hasLatitudeTarget) this.latitudeTarget.value = lat == null ? "" : String(lat)
139      if (this.hasLongitudeTarget) this.longitudeTarget.value = lng == null ? "" : String(lng)
140    } else {
141      if (this.hasLatitudeTarget) this.latitudeTarget.value = ""
142      if (this.hasLongitudeTarget) this.longitudeTarget.value = ""
143    }
144  }
145
146  _clearPlaceMeta() {
147    // If the user edits the address after selecting a place, don't keep stale structured metadata around.
148    if (this.hasFormattedTarget) this.formattedTarget.value = ""
149    if (this.hasPlaceIdTarget) this.placeIdTarget.value = ""
150    if (this.hasLatitudeTarget) this.latitudeTarget.value = ""
151    if (this.hasLongitudeTarget) this.longitudeTarget.value = ""
152  }
153}

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.