mapquest-agent-skills 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,243 @@
1
+ ---
2
+ name: mapquest-geocoding-patterns
3
+ description: Address-to-coordinate and coordinate-to-address conversion using the MapQuest Geocoding API. Covers forward geocoding, reverse geocoding, batch geocoding, quality codes, and response parsing.
4
+ ---
5
+
6
+ # MapQuest Geocoding Patterns
7
+
8
+ ## Overview
9
+
10
+ The MapQuest Geocoding API converts between addresses and geographic coordinates. Base URL: `https://www.mapquestapi.com/geocoding/v1`
11
+
12
+ Always append `?key=YOUR_API_KEY` to every request. Never hardcode the key — use environment variables (`MAPQUEST_API_KEY` or `VITE_MAPQUEST_API_KEY` for Vite projects).
13
+
14
+ ---
15
+
16
+ ## Geocoding Modes
17
+
18
+ ### 1. Forward Geocoding (Address → Lat/Lng)
19
+
20
+ **Endpoint:** `GET /geocoding/v1/address`
21
+
22
+ ```js
23
+ const response = await fetch(
24
+ `https://www.mapquestapi.com/geocoding/v1/address?key=${apiKey}&location=${encodeURIComponent(address)}`
25
+ );
26
+ const data = await response.json();
27
+ const { lat, lng } = data.results[0].locations[0].latLng;
28
+ ```
29
+
30
+ **Always check `geocodeQuality` before trusting the result:**
31
+
32
+ | Quality Code | Meaning | Trust Level |
33
+ |---|---|---|
34
+ | `POINT` | Rooftop/parcel level | ✅ High |
35
+ | `ADDRESS` | Interpolated street address | ✅ High |
36
+ | `INTERSECTION` | Street intersection | ✅ Medium |
37
+ | `STREET` | Street-level match | ⚠️ Medium |
38
+ | `ZIP` | ZIP code centroid | ⚠️ Low |
39
+ | `CITY` | City centroid | ❌ Low |
40
+ | `COUNTY` | County centroid | ❌ Low |
41
+ | `STATE` | State centroid | ❌ Low |
42
+ | `COUNTRY` | Country centroid | ❌ Very Low |
43
+
44
+ ```js
45
+ // Always validate quality before using coords
46
+ const location = data.results[0].locations[0];
47
+ const ACCEPTABLE_QUALITY = ['POINT', 'ADDRESS', 'INTERSECTION', 'STREET'];
48
+
49
+ if (!ACCEPTABLE_QUALITY.includes(location.geocodeQuality)) {
50
+ // Prompt user to refine their address
51
+ showAddressRefinementPrompt();
52
+ return;
53
+ }
54
+ ```
55
+
56
+ ### 2. Reverse Geocoding (Lat/Lng → Address)
57
+
58
+ **Endpoint:** `GET /geocoding/v1/reverse`
59
+
60
+ ```js
61
+ const response = await fetch(
62
+ `https://www.mapquestapi.com/geocoding/v1/reverse?key=${apiKey}&location=${lat},${lng}`
63
+ );
64
+ const data = await response.json();
65
+ const address = data.results[0].locations[0];
66
+ // address.street, address.adminArea5 (city), address.adminArea3 (state), address.postalCode
67
+ ```
68
+
69
+ ### 3. Batch Geocoding (Multiple Addresses)
70
+
71
+ **Endpoint:** `POST /geocoding/v1/batch`
72
+
73
+ Use batch geocoding when converting 2–100 addresses. More efficient than individual requests.
74
+
75
+ ```js
76
+ const response = await fetch(
77
+ `https://www.mapquestapi.com/geocoding/v1/batch?key=${apiKey}`,
78
+ {
79
+ method: 'POST',
80
+ headers: { 'Content-Type': 'application/json' },
81
+ body: JSON.stringify({
82
+ locations: [
83
+ '1600 Pennsylvania Ave NW, Washington, DC',
84
+ 'Times Square, New York, NY',
85
+ 'Golden Gate Bridge, San Francisco, CA'
86
+ ],
87
+ options: { thumbMaps: false } // Set false unless you need thumbnail map images
88
+ })
89
+ }
90
+ );
91
+ const data = await response.json();
92
+ // data.results is an array matching input order
93
+ ```
94
+
95
+ **Batch limits:** Max 100 locations per request. For larger sets, chunk the array.
96
+
97
+ ```js
98
+ function chunk(arr, size) {
99
+ return Array.from({ length: Math.ceil(arr.length / size) }, (_, i) =>
100
+ arr.slice(i * size, i * size + size)
101
+ );
102
+ }
103
+
104
+ async function batchGeocode(addresses) {
105
+ const chunks = chunk(addresses, 100);
106
+ const results = [];
107
+ for (const batch of chunks) {
108
+ const res = await geocodeBatch(batch);
109
+ results.push(...res.results);
110
+ // Add delay between batches to avoid rate limits
111
+ await new Promise(r => setTimeout(r, 200));
112
+ }
113
+ return results;
114
+ }
115
+ ```
116
+
117
+ ---
118
+
119
+ ## Request Options
120
+
121
+ ```js
122
+ {
123
+ options: {
124
+ thumbMaps: false, // Skip thumbnail images (saves bandwidth, faster response)
125
+ maxResults: 1, // Limit ambiguous address matches (default: 1)
126
+ ignoreLatLngInput: true, // Always geocode even if lat/lng supplied in location
127
+ boundingBox: { // Bias results to a bounding box
128
+ ul: { lat: 40.9, lng: -74.3 },
129
+ lr: { lat: 40.4, lng: -73.6 }
130
+ }
131
+ }
132
+ }
133
+ ```
134
+
135
+ ---
136
+
137
+ ## Response Parsing
138
+
139
+ ```js
140
+ // Safe response parser with fallbacks
141
+ function parseGeocodingResult(data) {
142
+ if (!data?.results?.[0]?.locations?.[0]) {
143
+ return null;
144
+ }
145
+ const loc = data.results[0].locations[0];
146
+ return {
147
+ lat: loc.latLng.lat,
148
+ lng: loc.latLng.lng,
149
+ quality: loc.geocodeQuality,
150
+ qualityCode: loc.geocodeQualityCode,
151
+ street: loc.street,
152
+ city: loc.adminArea5,
153
+ county: loc.adminArea4,
154
+ state: loc.adminArea3,
155
+ country: loc.adminArea1,
156
+ zip: loc.postalCode,
157
+ formatted: [loc.street, loc.adminArea5, loc.adminArea3, loc.postalCode]
158
+ .filter(Boolean)
159
+ .join(', ')
160
+ };
161
+ }
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Common Mistakes to Avoid
167
+
168
+ ❌ **Don't** use coordinates from a CITY or ZIP quality result for precise operations like routing or distance calculations — the coords are just the centroid.
169
+
170
+ ❌ **Don't** geocode on every keystroke. Debounce or wait for form submission.
171
+
172
+ ❌ **Don't** ignore the `info.statuscode` field. `0` = success. Any other value is an error.
173
+
174
+ ```js
175
+ if (data.info.statuscode !== 0) {
176
+ console.error('Geocoding error:', data.info.messages);
177
+ }
178
+ ```
179
+
180
+ ❌ **Don't** expose your API key in client-side code for production apps. Use a backend proxy.
181
+
182
+ ✅ **Do** set `thumbMaps: false` unless you actually need the thumbnail — it speeds up responses.
183
+
184
+ ✅ **Do** cache geocoding results for addresses you'll look up repeatedly.
185
+
186
+ ---
187
+
188
+ ## Structured Address Input (More Accurate)
189
+
190
+ Instead of a raw string, pass a structured location object for higher accuracy:
191
+
192
+ ```js
193
+ const response = await fetch(
194
+ `https://www.mapquestapi.com/geocoding/v1/address?key=${apiKey}`,
195
+ {
196
+ method: 'POST',
197
+ headers: { 'Content-Type': 'application/json' },
198
+ body: JSON.stringify({
199
+ location: {
200
+ street: '1090 N Charlotte St',
201
+ city: 'Lancaster',
202
+ state: 'PA',
203
+ postalCode: '17603',
204
+ country: 'US'
205
+ },
206
+ options: { thumbMaps: false }
207
+ })
208
+ }
209
+ );
210
+ ```
211
+
212
+ ---
213
+
214
+ ## Error Handling Template
215
+
216
+ ```js
217
+ async function geocodeAddress(address, apiKey) {
218
+ try {
219
+ const url = `https://www.mapquestapi.com/geocoding/v1/address?key=${apiKey}&location=${encodeURIComponent(address)}&thumbMaps=false`;
220
+ const response = await fetch(url);
221
+
222
+ if (!response.ok) {
223
+ throw new Error(`HTTP error: ${response.status}`);
224
+ }
225
+
226
+ const data = await response.json();
227
+
228
+ if (data.info.statuscode !== 0) {
229
+ throw new Error(`MapQuest error: ${data.info.messages.join(', ')}`);
230
+ }
231
+
232
+ const result = parseGeocodingResult(data);
233
+ if (!result) {
234
+ throw new Error('No results found for this address');
235
+ }
236
+
237
+ return result;
238
+ } catch (error) {
239
+ console.error('Geocoding failed:', error);
240
+ throw error;
241
+ }
242
+ }
243
+ ```
@@ -0,0 +1,183 @@
1
+ ---
2
+ name: mapquest-key-security
3
+ description: Best practices for securing MapQuest API keys. Covers environment variables, referrer restrictions, server-side proxying, key rotation, and incident response when a key is exposed.
4
+ ---
5
+
6
+ # MapQuest API Key Security
7
+
8
+ ## Overview
9
+
10
+ Your MapQuest API key is a secret credential. Exposing it allows others to use your quota, incur charges on your account, and potentially abuse MapQuest services. Always treat API keys like passwords.
11
+
12
+ ---
13
+
14
+ ## The Golden Rules
15
+
16
+ 1. **Never hardcode an API key in source code**
17
+ 2. **Never commit a key to version control**
18
+ 3. **Use referrer restrictions in the MapQuest developer portal**
19
+ 4. **For server-rendered or backend apps: proxy all MapQuest requests through your server**
20
+ 5. **Rotate keys if you suspect exposure**
21
+
22
+ ---
23
+
24
+ ## Environment Variable Storage
25
+
26
+ ### Frontend (Vite / React / Vue)
27
+
28
+ ```bash
29
+ # .env.local (never commit this file)
30
+ VITE_MAPQUEST_API_KEY=your_key_here
31
+ ```
32
+
33
+ ```js
34
+ // Access in code:
35
+ const apiKey = import.meta.env.VITE_MAPQUEST_API_KEY;
36
+ ```
37
+
38
+ ```
39
+ # .gitignore — ensure .env files are excluded
40
+ .env
41
+ .env.local
42
+ .env*.local
43
+ ```
44
+
45
+ ### Backend (Node.js)
46
+
47
+ ```bash
48
+ # .env
49
+ MAPQUEST_API_KEY=your_key_here
50
+ ```
51
+
52
+ ```js
53
+ require('dotenv').config();
54
+ const apiKey = process.env.MAPQUEST_API_KEY;
55
+ ```
56
+
57
+ ### Never do this:
58
+
59
+ ```js
60
+ // ❌ WRONG — key is visible in source code and bundle
61
+ const apiKey = 'Ab1Cd2Ef3Gh4Ij5Kl6Mn7Op8Qr9';
62
+
63
+ // ❌ WRONG — key in git history forever
64
+ // git commit -m "added api key"
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Referrer Restrictions (Developer Portal)
70
+
71
+ In the [MapQuest Developer Portal](https://developer.mapquest.com), set **Allowed Referrers** for each key:
72
+
73
+ - `https://yourdomain.com/*` — production
74
+ - `https://staging.yourdomain.com/*` — staging
75
+ - `http://localhost:*` — local development
76
+
77
+ This limits the key to only work when requests originate from your domains. Client-side keys with referrer restrictions are significantly safer than unrestricted keys.
78
+
79
+ **Limitation:** Referrer headers can be spoofed by determined bad actors. For truly sensitive apps, use server-side proxying instead.
80
+
81
+ ---
82
+
83
+ ## Server-Side Proxy (Recommended for Production)
84
+
85
+ Never send MapQuest API requests directly from client-side code in production. Instead, proxy through your backend:
86
+
87
+ ```js
88
+ // Express.js proxy route
89
+ const express = require('express');
90
+ const router = express.Router();
91
+
92
+ router.get('/geocode', async (req, res) => {
93
+ const { address } = req.query;
94
+
95
+ // Validate input
96
+ if (!address || address.length > 500) {
97
+ return res.status(400).json({ error: 'Invalid address' });
98
+ }
99
+
100
+ // Optional: rate limit per user/IP here
101
+
102
+ try {
103
+ const response = await fetch(
104
+ `https://www.mapquestapi.com/geocoding/v1/address?key=${process.env.MAPQUEST_API_KEY}&location=${encodeURIComponent(address)}&thumbMaps=false`
105
+ );
106
+ const data = await response.json();
107
+ res.json(data);
108
+ } catch (err) {
109
+ res.status(500).json({ error: 'Geocoding service unavailable' });
110
+ }
111
+ });
112
+ ```
113
+
114
+ ```js
115
+ // Client-side — call your proxy, not MapQuest directly
116
+ const response = await fetch(`/api/geocode?address=${encodeURIComponent(address)}`);
117
+ ```
118
+
119
+ **Benefits of proxying:**
120
+ - API key never exposed to browser
121
+ - You can rate-limit per user
122
+ - You can cache responses
123
+ - You can log and monitor usage
124
+
125
+ ---
126
+
127
+ ## Key Rotation Strategy
128
+
129
+ Create multiple keys in the MapQuest Developer Portal:
130
+
131
+ | Key Name | Purpose | Restrictions |
132
+ |---|---|---|
133
+ | `production-web` | Live site | Referrer: `https://yourdomain.com/*` |
134
+ | `staging-web` | Staging environment | Referrer: `https://staging.yourdomain.com/*` |
135
+ | `development` | Local dev | Referrer: `http://localhost:*` |
136
+ | `server-side` | Backend proxy | No referrer (IP-restricted if possible) |
137
+
138
+ Rotate keys every 90 days or immediately after any potential exposure.
139
+
140
+ ---
141
+
142
+ ## Incident Response: Exposed Key
143
+
144
+ If you suspect a key was exposed (e.g., committed to a public repo):
145
+
146
+ 1. **Immediately regenerate/revoke the key** in the MapQuest Developer Portal
147
+ 2. **Deploy a new key** from your secure environment variable store
148
+ 3. **Purge git history** if committed (use `git filter-branch` or BFG Repo Cleaner)
149
+ 4. **Check MapQuest usage logs** for unusual activity
150
+ 5. **Notify your team** and update deployment pipelines
151
+
152
+ ```bash
153
+ # If key was committed — remove from git history
154
+ # Install BFG: https://rtyley.github.io/bfg-repo-cleaner/
155
+ bfg --replace-text secrets.txt my-repo.git
156
+ git push --force
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Checklist for Every Project
162
+
163
+ - [ ] API key stored in `.env` / environment variable, never in source code
164
+ - [ ] `.env` files added to `.gitignore`
165
+ - [ ] Referrer restrictions configured in MapQuest Developer Portal
166
+ - [ ] Separate keys for production, staging, and development
167
+ - [ ] Server-side proxy implemented for production (recommended)
168
+ - [ ] Key rotation schedule documented (every 90 days)
169
+ - [ ] Team members aware not to log or share keys in chat/tickets
170
+
171
+ ---
172
+
173
+ ## What Not to Do
174
+
175
+ ❌ Don't log API keys in console.log or server logs
176
+
177
+ ❌ Don't share API keys in Slack, email, or issue trackers
178
+
179
+ ❌ Don't use the same key for development and production
180
+
181
+ ❌ Don't store keys in local storage or cookies (accessible to JS/XSS)
182
+
183
+ ❌ Don't put keys in URLs (they appear in server logs and browser history)
@@ -0,0 +1,286 @@
1
+ ---
2
+ name: mapquest-search-ahead
3
+ description: Typeahead and autocomplete using the MapQuest Search Ahead API. Covers debouncing, result categories, geographic bias, collection types, and chaining with the Geocoding API.
4
+ ---
5
+
6
+ # MapQuest Search Ahead Patterns
7
+
8
+ ## Overview
9
+
10
+ The Search Ahead API powers autocomplete/typeahead address and POI search. Base URL: `https://www.mapquestapi.com/search/v3/prediction`
11
+
12
+ Use this for live search inputs, not for batch geocoding or one-shot address resolution. For a single definitive address lookup, use the Geocoding API.
13
+
14
+ ---
15
+
16
+ ## Basic Search Ahead Request
17
+
18
+ ```js
19
+ const response = await fetch(
20
+ `https://www.mapquestapi.com/search/v3/prediction?key=${apiKey}&q=${encodeURIComponent(query)}&collection=address,adminArea,poi`
21
+ );
22
+ const data = await response.json();
23
+ // data.results is an array of prediction objects
24
+ ```
25
+
26
+ ---
27
+
28
+ ## Collection Types
29
+
30
+ The `collection` parameter controls what types of results are returned. Always specify what you need — don't request everything.
31
+
32
+ | Collection | Description | Use When |
33
+ |---|---|---|
34
+ | `address` | Street addresses | Address entry forms |
35
+ | `adminArea` | Cities, states, countries | Location pickers |
36
+ | `airport` | Airport names + IATA codes | Travel apps |
37
+ | `category` | Category labels (e.g., "restaurants") | Search-by-type UIs |
38
+ | `franchise` | Chain business names (e.g., "Starbucks") | Store locators |
39
+ | `poi` | Individual points of interest | General search |
40
+
41
+ ```js
42
+ // Address-only (fastest, most relevant for delivery/shipping forms)
43
+ collection=address
44
+
45
+ // Address + city for flexible location search
46
+ collection=address,adminArea
47
+
48
+ // Full search (POI + address + city)
49
+ collection=poi,address,adminArea
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Geographic Bias
55
+
56
+ Bias results toward a location to get more relevant suggestions:
57
+
58
+ ```js
59
+ const params = new URLSearchParams({
60
+ key: apiKey,
61
+ q: query,
62
+ collection: 'address,poi',
63
+ location: `${userLng},${userLat}`, // NOTE: lng,lat order (not lat,lng!)
64
+ limit: 5,
65
+ });
66
+ ```
67
+
68
+ **Critical:** The `location` parameter uses **longitude, latitude** order (opposite of most MapQuest parameters).
69
+
70
+ ---
71
+
72
+ ## Debouncing — Required
73
+
74
+ Always debounce Search Ahead requests. Fire no more than once per 300–500ms. Never call the API on every keystroke.
75
+
76
+ ```js
77
+ // Vanilla JS debounce
78
+ function debounce(fn, delay) {
79
+ let timer;
80
+ return (...args) => {
81
+ clearTimeout(timer);
82
+ timer = setTimeout(() => fn(...args), delay);
83
+ };
84
+ }
85
+
86
+ const searchInput = document.getElementById('search');
87
+ const resultsContainer = document.getElementById('results');
88
+
89
+ const handleSearch = debounce(async (query) => {
90
+ if (query.length < 2) {
91
+ resultsContainer.innerHTML = '';
92
+ return;
93
+ }
94
+
95
+ const data = await searchAhead(query);
96
+ renderResults(data.results);
97
+ }, 300);
98
+
99
+ searchInput.addEventListener('input', e => handleSearch(e.target.value));
100
+ ```
101
+
102
+ ```js
103
+ // React hook with debounce
104
+ import { useState, useEffect } from 'react';
105
+
106
+ function useSearchAhead(apiKey, debounceMs = 300) {
107
+ const [query, setQuery] = useState('');
108
+ const [results, setResults] = useState([]);
109
+ const [loading, setLoading] = useState(false);
110
+
111
+ useEffect(() => {
112
+ if (query.length < 2) {
113
+ setResults([]);
114
+ return;
115
+ }
116
+
117
+ const timer = setTimeout(async () => {
118
+ setLoading(true);
119
+ try {
120
+ const data = await fetchPredictions(query, apiKey);
121
+ setResults(data.results || []);
122
+ } catch (e) {
123
+ console.error(e);
124
+ } finally {
125
+ setLoading(false);
126
+ }
127
+ }, debounceMs);
128
+
129
+ return () => clearTimeout(timer);
130
+ }, [query, apiKey, debounceMs]);
131
+
132
+ return { query, setQuery, results, loading };
133
+ }
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Parsing Results
139
+
140
+ ```js
141
+ function parsePrediction(result) {
142
+ return {
143
+ id: result.id,
144
+ displayString: result.displayString, // Human-readable label
145
+ place: result.place, // Detailed place info
146
+ collection: result.collection, // 'address', 'poi', etc.
147
+ // For addresses:
148
+ street: result.place?.properties?.street,
149
+ city: result.place?.properties?.city,
150
+ state: result.place?.properties?.stateCode,
151
+ zip: result.place?.properties?.postalCode,
152
+ country: result.place?.properties?.countryCode,
153
+ // Coordinates (if available in result):
154
+ lat: result.place?.geometry?.coordinates?.[1],
155
+ lng: result.place?.geometry?.coordinates?.[0],
156
+ };
157
+ }
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Chaining Search Ahead → Geocoding
163
+
164
+ Search Ahead results often don't include precise lat/lng coordinates. Chain with the Geocoding API to resolve:
165
+
166
+ ```js
167
+ async function selectPrediction(prediction, apiKey) {
168
+ // Some predictions include coordinates directly
169
+ if (prediction.place?.geometry?.coordinates) {
170
+ const [lng, lat] = prediction.place.geometry.coordinates;
171
+ return { lat, lng, label: prediction.displayString };
172
+ }
173
+
174
+ // Otherwise, geocode the display string
175
+ const geoResult = await geocodeAddress(prediction.displayString, apiKey);
176
+ return {
177
+ lat: geoResult.lat,
178
+ lng: geoResult.lng,
179
+ label: prediction.displayString,
180
+ };
181
+ }
182
+ ```
183
+
184
+ ---
185
+
186
+ ## Full Autocomplete Component (Vanilla JS)
187
+
188
+ ```js
189
+ class MapQuestAutocomplete {
190
+ constructor(inputEl, apiKey, onSelect) {
191
+ this.input = inputEl;
192
+ this.apiKey = apiKey;
193
+ this.onSelect = onSelect;
194
+ this.dropdown = this.createDropdown();
195
+ this.debounceTimer = null;
196
+
197
+ this.input.addEventListener('input', () => this.handleInput());
198
+ this.input.addEventListener('blur', () => setTimeout(() => this.hide(), 200));
199
+ document.addEventListener('click', e => {
200
+ if (!this.input.contains(e.target)) this.hide();
201
+ });
202
+ }
203
+
204
+ createDropdown() {
205
+ const el = document.createElement('ul');
206
+ el.style.cssText = 'position:absolute;background:#fff;border:1px solid #ccc;list-style:none;margin:0;padding:0;width:100%;z-index:1000;display:none';
207
+ this.input.parentElement.style.position = 'relative';
208
+ this.input.parentElement.appendChild(el);
209
+ return el;
210
+ }
211
+
212
+ handleInput() {
213
+ clearTimeout(this.debounceTimer);
214
+ const q = this.input.value.trim();
215
+ if (q.length < 2) { this.hide(); return; }
216
+ this.debounceTimer = setTimeout(() => this.search(q), 300);
217
+ }
218
+
219
+ async search(q) {
220
+ const url = `https://www.mapquestapi.com/search/v3/prediction?key=${this.apiKey}&q=${encodeURIComponent(q)}&collection=address,adminArea,poi&limit=5`;
221
+ const res = await fetch(url);
222
+ const data = await res.json();
223
+ this.show(data.results || []);
224
+ }
225
+
226
+ show(results) {
227
+ this.dropdown.innerHTML = '';
228
+ results.forEach(r => {
229
+ const li = document.createElement('li');
230
+ li.textContent = r.displayString;
231
+ li.style.cssText = 'padding:8px 12px;cursor:pointer';
232
+ li.addEventListener('mouseover', () => li.style.background = '#f0f0f0');
233
+ li.addEventListener('mouseout', () => li.style.background = '');
234
+ li.addEventListener('click', () => {
235
+ this.input.value = r.displayString;
236
+ this.hide();
237
+ this.onSelect(r);
238
+ });
239
+ this.dropdown.appendChild(li);
240
+ });
241
+ this.dropdown.style.display = results.length ? 'block' : 'none';
242
+ }
243
+
244
+ hide() { this.dropdown.style.display = 'none'; }
245
+ }
246
+
247
+ // Usage:
248
+ const autocomplete = new MapQuestAutocomplete(
249
+ document.getElementById('address-input'),
250
+ 'YOUR_API_KEY',
251
+ async (prediction) => {
252
+ const location = await selectPrediction(prediction, 'YOUR_API_KEY');
253
+ map.setCenter([location.lat, location.lng]);
254
+ }
255
+ );
256
+ ```
257
+
258
+ ---
259
+
260
+ ## Common Mistakes to Avoid
261
+
262
+ ❌ **Don't** fire Search Ahead on every keypress — always debounce.
263
+
264
+ ❌ **Don't** set `limit` higher than needed (max 10). Larger result sets are slower and rarely improve UX.
265
+
266
+ ❌ **Don't** confuse `location` parameter order. Search Ahead uses **lng,lat**. Geocoding API uses **lat,lng**. Always double-check.
267
+
268
+ ❌ **Don't** assume Search Ahead results include coordinates. They may not — always check before using and fall back to geocoding.
269
+
270
+ ✅ **Do** set a minimum character threshold (2–3 chars) before calling the API.
271
+
272
+ ✅ **Do** cancel in-flight requests when a newer query supersedes them (use AbortController).
273
+
274
+ ```js
275
+ let controller;
276
+
277
+ async function fetchPredictions(q, apiKey) {
278
+ if (controller) controller.abort();
279
+ controller = new AbortController();
280
+
281
+ const res = await fetch(url, { signal: controller.signal });
282
+ return res.json();
283
+ }
284
+ ```
285
+
286
+ ✅ **Do** handle network errors gracefully — the dropdown should fail silently, not crash the page.