1(function (TomSelect, gaHelp) { 2 (function main() { 3 exportConfig(); 4 initializeSearches(); 5 })(); 6 7 /** 8 * Sets up the main Location Searches (in the header and home page). 9 */ 10 function initializeSearches() { 11 document.querySelectorAll(".location-search").forEach((input) => { 12 const select = initializeLocationSearch(input, false); 13 select.on("change", _handleLocationSelect); 14 }); 15 } 16 17 /** 18 * Builds a Location Search field. 19 * @param {HTMLSelectElement} input - The select element to house the Location Search 20 * @param {Boolean} [addressOption = false] - Whether to allow address and lat/lon searches 21 * @return {TomSelect} - A generated Location Search field 22 */ 23 function initializeLocationSearch(input, addressOption = false) { 24 const urlBase = "https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/suggest?text="; 25 const latLonBase = 26 "https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates" + 27 "?f=pjson&maxLocations=1&forStorage=false&category=XY&Address="; 28 let urlSuffix = 29 "&f=json&countryCode=USA&returnCollections=false&category=Postal,Neighborhood,District,City,Subregion,Region"; 30 if (addressOption) { 31 urlSuffix = "&f=json&countryCode=USA&returnCollections=false&category=Postal,Neighborhood,District,City,Address"; 32 input.setAttribute("placeholder", "Enter Address, Zip Code, City, or Long/Lat Location"); 33 } else { 34 input.setAttribute("placeholder", "Enter Zip Code, City, County, or State"); 35 } 36 37 const settings = { 38 valueField: "magicKey", 39 labelField: "location", 40 searchField: [], 41 maxOptions: 5, 42 maxItems: 1, 43 create: false, 44 persist: false, 45 shouldLoad(query) { 46 return query.length > 2; 47 }, 48 load(query, callback) { 49 let url; 50 // Regex to detect long/lat inputs 51 if (addressOption && query.match(/^((\-?|\+?)?\d+(\.\d+)?),\s*((\-?|\+?)?\d+(\.\d+)?)$/)) { 52 url = latLonBase + encodeURIComponent(query); 53 } else { 54 url = urlBase + encodeURIComponent(query) + urlSuffix; 55 } 56 fetch(url) 57 .then((response) => response.json()) 58 .then((json) => { 59 this.clearOptions(); 60 if (json.candidates) { 61 convertCandidates(json); 62 } 63 callback(filterSuggestions(json.suggestions)); 64 }) 65 .catch(() => { 66 callback(); 67 }); 68 }, 69 render: { 70 options: [], 71 option(item, escape) { 72 return `<div data-magicKey=${escape(item.magicKey)}>${escape(item.text)}</div>`; 73 }, 74 item(item, escape) { 75 return `<div id="selected-location" data-magicKey=${escape(item.magicKey)}>${escape(item.text)}</div>`; 76 }, 77 }, 78 plugins: { 79 dropdown_header: { 80 title: "Select a location from the list below", 81 labelClass: "dropdown-header-label font-sans-3xs", 82 html: (data) => { 83 return `<div class="${data.headerClass}"> 84 <div class="${data.titleRowClass}"> 85 <span class="${data.labelClass}">${data.title}</span> 86 </div> 87 </div>`; 88 }, 89 }, 90 }, 91 }; 92 return new TomSelect(input, settings); 93 } 94 95 /** 96 * Responses to lon/lat searches need to fit the generic syntax. This replaces the values of the passed array. 97 * @param {{}} json - List of possible locations for the user provided string. 98 * @return {undefined|bool} - 99 */ 100 function convertCandidates(json) { 101 const coord = validateCoordinates(json.candidates[0]); 102 if (!coord) { 103 return false; 104 } 105 json.suggestions = [ 106 { 107 text: `X:${coord.x}, Y:${coord.y}`, 108 magicKey: "", 109 }, 110 ]; 111 } 112 113 /** 114 * Validator for US/PR coordinates 115 * @param {{}} coord - 116 * @return {{ x: number, y: number }|boolean} - The coordinate if it is in the US or false. 117 */ 118 function validateCoordinates(coord) { 119 const x = coord.location.x; 120 const y = coord.location.y;
121 let checked = false; 122 const bb = { 123 conus: [-124.73, 24.51, -66.98, 49.38], 124 AK: [-179.15, 51.22, -129.9801315, 71.35], 125 HI: [-160.2500505, 18.92, -154.8100651, 22.23], 126 PR: [-67.9402974, 17.88, -65.2203047, 18.52], 127 }; 128 Object.values(bb).forEach((bound) => { 129 if (bound[0] <= x && bound[1] <= y && bound[2] >= x && bound[3] >= y) { 130 checked = { x, y }; 131 } 132 }); 133 return checked; 134 } 135 136 /** 137 * Filters out erroneous suggestions. 138 * 139 * ArcGIS bug currently returns Palau addresses so "PLW" are removed. The API also returns Washington County in DC 140 * which hasn't existed since 1871. 141 * @param {[]} suggestions - Array of potential locations returned by the api 142 * @return {[]} Filtered list 143 */ 144 function filterSuggestions(suggestions) { 145 return suggestions 146 .filter((item) => { 147 return !item.text.endsWith("PLW"); 148 }) 149 .filter((item) => { 150 return item.text !== "Washington, DC, USA (Washington County)"; 151 }); 152 } 153 154 /** 155 * Processes the selection of an item within the main Location Searches 156 */ 157 function _handleLocationSelect() { 158 const val = this.getValue(); 159 const text = this.getItem(val).innerText; 160 registerSearchEvent(this.input, val); 161 document.location.href = "/location/" + encodeURIComponent(text) + "?key=" + val; 162 } 163 164 /** 165 * Logs a GA4 event associated with the Location Search. 166 * @param {HTMLSelectElement} input - The select element that is the event target 167 * @param {string} value - The text content of the search 168 */ 169 function registerSearchEvent(input, value) { 170 const section = input.id === "front-page-location-search" ? "main" : "menu"; 171 gaHelp.registerEvent("search", { 172 action: "location", 173 direction: value, 174 section, 175 }); 176 } 177 178 /** 179 * Generic export function 180 */ 181 function exportConfig() { 182 window.locationSearch = { 183 initializeLocationSearch, 184 }; 185 } 186})(window.TomSelect, window.gaHelp);
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.