1// Simple DNA Location Search JavaScript 2jQuery(document).ready(function($){ 3 4 // US States data 5 const states = [ 6 { name: 'Alabama', code: 'AL' }, 7 { name: 'Alaska', code: 'AK' }, 8 { name: 'Arizona', code: 'AZ' }, 9 { name: 'Arkansas', code: 'AR' }, 10 { name: 'California', code: 'CA' }, 11 { name: 'Colorado', code: 'CO' }, 12 { name: 'Connecticut', code: 'CT' }, 13 { name: 'Delaware', code: 'DE' }, 14 { name: 'Florida', code: 'FL' }, 15 { name: 'Georgia', code: 'GA' }, 16 { name: 'Hawaii', code: 'HI' }, 17 { name: 'Idaho', code: 'ID' }, 18 { name: 'Illinois', code: 'IL' }, 19 { name: 'Indiana', code: 'IN' }, 20 { name: 'Iowa', code: 'IA' }, 21 { name: 'Kansas', code: 'KS' }, 22 { name: 'Kentucky', code: 'KY' }, 23 { name: 'Louisiana', code: 'LA' }, 24 { name: 'Maine', code: 'ME' }, 25 { name: 'Maryland', code: 'MD' }, 26 { name: 'Massachusetts', code: 'MA' }, 27 { name: 'Michigan', code: 'MI' }, 28 { name: 'Minnesota', code: 'MN' }, 29 { name: 'Mississippi', code: 'MS' }, 30 { name: 'Missouri', code: 'MO' }, 31 { name: 'Montana', code: 'MT' }, 32 { name: 'Nebraska', code: 'NE' }, 33 { name: 'Nevada', code: 'NV' }, 34 { name: 'New Hampshire', code: 'NH' }, 35 { name: 'New Jersey', code: 'NJ' }, 36 { name: 'New Mexico', code: 'NM' }, 37 { name: 'New York', code: 'NY' }, 38 { name: 'North Carolina', code: 'NC' }, 39 { name: 'North Dakota', code: 'ND' }, 40 { name: 'Ohio', code: 'OH' }, 41 { name: 'Oklahoma', code: 'OK' }, 42 { name: 'Oregon', code: 'OR' }, 43 { name: 'Pennsylvania', code: 'PA' }, 44 { name: 'Rhode Island', code: 'RI' }, 45 { name: 'South Carolina', code: 'SC' }, 46 { name: 'South Dakota', code: 'SD' }, 47 { name: 'Tennessee', code: 'TN' }, 48 { name: 'Texas', code: 'TX' }, 49 { name: 'Utah', code: 'UT' }, 50 { name: 'Vermont', code: 'VT' }, 51 { name: 'Virginia', code: 'VA' }, 52 { name: 'Washington', code: 'WA' }, 53 { name: 'West Virginia', code: 'WV' }, 54 { name: 'Wisconsin', code: 'WI' }, 55 { name: 'Wyoming', code: 'WY' } 56 ]; 57 58 // Global function to initialize search 59 window.initSimpleDnaSearch = function(uniqueId) { 60 const searchInput = document.getElementById(uniqueId + '-input'); 61 const searchButton = document.getElementById(uniqueId + '-btn'); 62 const suggestionsContainer = document.getElementById(uniqueId + '-suggestions'); 63 64 if (!searchInput || !searchButton || !suggestionsContainer) { 65 console.error('Simple DNA Search: Required elements not found for ID: ' + uniqueId); 66 return; 67 } 68 69 let currentSuggestions = []; 70 let selectedIndex = -1; 71 let searchTimeout; 72 73 // URLs for redirection 74 const stateUrlPattern = 'https://www.facednatest.com/statedirs/'; 75 const zipUrlBase = 'https://facednatest.com/dna-testing-locations/'; 76 const defaultDistance = '25'; 77 78 // Check if input is a zip code (5 digits) 79 function isZipCode(input) { 80 return /^\d{5}$/.test(input.trim()); 81 } 82 83 // Check if input could be a zip code (1-5 digits) 84 function couldBeZipCode(input) { 85 return /^\d{1,5}$/.test(input.trim()); 86 } 87 88 // Filter states based on input 89 function filterStates(input) { 90 const query = input.toLowerCase().trim(); 91 if (!query || couldBeZipCode(query)) return []; 92 93 return states.filter(state => 94 state.name.toLowerCase().includes(query) || 95 state.code.toLowerCase() === query 96 ).slice(0, 5); 97 } 98 99 // Show suggestions 100 function showSuggestions(input) { 101 const query = input.trim(); 102 103 if (!query || query.length < 2) { 104 hideSuggestions(); 105 return; 106 } 107 108 let suggestions = []; 109 110 // Handle zip code input 111 if (couldBeZipCode(query)) { 112 if (query.length === 5) { 113 suggestions.push({ 114 text: query, 115 type: 'ZIP', 116 value: query, 117 complete: true 118 }); 119 } else { 120 suggestions.push({ 121 text: query + '...', 122 type: 'ZIP', 123 value: query, 124 complete: false 125 }); 126 } 127 } else { 128 // Add state suggestions 129 const stateMatches = filterStates(query);
130 stateMatches.forEach(state => { 131 suggestions.push({ 132 text: state.name, 133 type: 'STATE', 134 value: state.code, 135 complete: true 136 }); 137 }); 138 } 139 140 currentSuggestions = suggestions; 141 selectedIndex = -1; 142 143 if (suggestions.length > 0) { 144 renderSuggestions(suggestions); 145 suggestionsContainer.style.display = 'block'; 146 } else { 147 hideSuggestions(); 148 } 149 } 150 151 // Render suggestions 152 function renderSuggestions(suggestions) { 153 suggestionsContainer.innerHTML = suggestions.map((suggestion, index) => { 154 const selectedClass = index === selectedIndex ? 'selected' : ''; 155 const typeClass = suggestion.type.toLowerCase(); 156 return ` 157 <div class="simple-suggestion-item ${selectedClass}" 158 data-index="${index}"> 159 <span class="simple-suggestion-text">${suggestion.text}</span> 160 <span class="simple-suggestion-type ${typeClass}">${suggestion.type}</span> 161 </div> 162 `; 163 }).join(''); 164 165 // Add click events 166 suggestionsContainer.querySelectorAll('.simple-suggestion-item').forEach((item, index) => { 167 item.addEventListener('click', () => selectSuggestion(index)); 168 }); 169 } 170 171 // Hide suggestions 172 function hideSuggestions() { 173 suggestionsContainer.style.display = 'none'; 174 currentSuggestions = []; 175 selectedIndex = -1; 176 } 177 178 // Select suggestion 179 function selectSuggestion(index) { 180 if (index >= 0 && index < currentSuggestions.length) { 181 const suggestion = currentSuggestions[index]; 182 183 if (!suggestion.complete) return; // Don't select incomplete entries 184 185 searchInput.value = suggestion.text; 186 hideSuggestions(); 187 performSearch(suggestion.value, suggestion.type); 188 } 189 } 190 191 // Perform search 192 function performSearch(value, type = null) { 193 const trimmedValue = value.trim(); 194 if (!trimmedValue) return; 195 196 // Determine search type if not specified 197 if (!type) { 198 if (isZipCode(trimmedValue)) { 199 type = 'ZIP'; 200 } else { 201 const matchedState = states.find(state => 202 state.name.toLowerCase() === trimmedValue.toLowerCase() || 203 state.code.toLowerCase() === trimmedValue.toLowerCase() 204 ); 205 if (matchedState) { 206 type = 'STATE'; 207 value = matchedState.code; 208 } 209 } 210 } 211 212 // Redirect based on type 213 if (type === 'ZIP') { 214 window.location.href = `${zipUrlBase}?zc=${encodeURIComponent(trimmedValue)}&zd=${encodeURIComponent(defaultDistance)}`; 215 } else if (type === 'STATE') { 216 window.location.href = `${stateUrlPattern}${encodeURIComponent(value)}`; 217 } else { 218 alert('Please enter a valid state name or 5-digit zip code'); 219 } 220 } 221 222 // Input event with debouncing 223 searchInput.addEventListener('input', function() { 224 const value = this.value; 225 226 // Limit zip code input to 5 digits 227 if (couldBeZipCode(value) && value.length > 5) { 228 this.value = value.slice(0, 5); 229 return; 230 } 231 232 // Debounce suggestions 233 clearTimeout(searchTimeout); 234 searchTimeout = setTimeout(() => { 235 showSuggestions(this.value); 236 }, 150); 237 }); 238 239 // Keypress validation for zip codes 240 searchInput.addEventListener('keypress', function(e) { 241 const value = this.value;
242 const key = e.key; 243 244 // If current value looks like a zip code 245 if (couldBeZipCode(value) || (value === '' && /^\d$/.test(key))) { 246 // Allow only digits and special keys 247 if (!/^\d$/.test(key) && !['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Enter'].includes(key)) { 248 e.preventDefault(); 249 } 250 // Prevent more than 5 digits 251 if (value.length >= 5 && /^\d$/.test(key)) { 252 e.preventDefault(); 253 } 254 } 255 }); 256 257 // Keyboard navigation 258 searchInput.addEventListener('keydown', function(e) { 259 if (currentSuggestions.length === 0) { 260 if (e.key === 'Enter') { 261 e.preventDefault(); 262 performSearch(this.value); 263 } 264 return; 265 } 266 267 switch(e.key) { 268 case 'ArrowDown': 269 e.preventDefault(); 270 selectedIndex = Math.min(selectedIndex + 1, currentSuggestions.length - 1); 271 renderSuggestions(currentSuggestions); 272 break; 273 case 'ArrowUp': 274 e.preventDefault(); 275 selectedIndex = Math.max(selectedIndex - 1, -1); 276 renderSuggestions(currentSuggestions); 277 break; 278 case 'Enter': 279 e.preventDefault(); 280 if (selectedIndex >= 0) { 281 selectSuggestion(selectedIndex); 282 } else { 283 performSearch(this.value); 284 } 285 break; 286 case 'Escape': 287 hideSuggestions(); 288 break; 289 } 290 }); 291 292 // Search button click 293 searchButton.addEventListener('click', function() { 294 performSearch(searchInput.value); 295 }); 296 297 // Hide suggestions when clicking outside 298 document.addEventListener('click', function(e) { 299 if (!e.target.closest(`#${uniqueId}-input`) && !e.target.closest(`#${uniqueId}-suggestions`)) { 300 hideSuggestions(); 301 } 302 }); 303 304 // Focus management 305 searchInput.addEventListener('focus', function() { 306 if (this.value.trim() && this.value.length >= 2) { 307 showSuggestions(this.value); 308 } 309 }); 310 }; 311 312});
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.