Skip to content

Commit

Permalink
Merge pull request #39 from Woosmap/dev/localities-details-api
Browse files Browse the repository at this point in the history
Localties Details Api Samples
  • Loading branch information
gaelsimon authored Mar 11, 2024
2 parents 62dab23 + fb00924 commit 018310a
Show file tree
Hide file tree
Showing 12 changed files with 1,122 additions and 2 deletions.
2 changes: 1 addition & 1 deletion samples/distance-matrix-advanced/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
flex-grow: 1;
overflow: hidden;
overflow-y: auto;
padding: 0 10px 10px;
padding: 0 10px 40px;
}

input,
Expand Down
21 changes: 21 additions & 0 deletions samples/localities-api-details-postalcodes/index.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{% extends '../../src/_includes/layout.njk' %}
{% block html %}
<!-- [START woosmap_{{ tag }}_div] -->
<div id="app">
<div id="map"></div>
<div id="autocomplete-container">
{% svgIcon 'search.svg' %}
<input
type="text"
id="autocomplete-input"
placeholder="Type in a UK postal code..."
autocomplete="off"
/>
<button aria-label="Clear" class="clear-searchButton" type="button">
{% svgIcon 'clear.svg' %}
</button>
<ul id="suggestions-list"></ul>
</div>
</div>
<!-- [END woosmap_{{ tag }}_div] -->
{% endblock %}
320 changes: 320 additions & 0 deletions samples/localities-api-details-postalcodes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
// [START woosmap_localities_api_details_postalcodes]
const woosmap_key = "YOUR_API_KEY";
const countryRestrictions = ["GB", "JE", "IM", "GG"];
const types = ["postal_code"];
let map: woosmap.map.Map;
let debouncedAutocomplete: (
...args: any[]
) => Promise<woosmap.map.localities.LocalitiesAutocompleteResponse>;
let inputElement: HTMLInputElement;
let suggestionsList: HTMLUListElement;
let clearSearchBtn: HTMLButtonElement;
let markerAddress: woosmap.map.Marker;

function init(): void {
if (inputElement && suggestionsList) {
inputElement.addEventListener("input", handleAutocomplete);
inputElement.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
const firstLi = suggestionsList.querySelector("li");
if (firstLi) {
firstLi.click();
}
}
});
}
clearSearchBtn.addEventListener("click", () => {
inputElement.value = "";
suggestionsList.style.display = "none";
clearSearchBtn.style.display = "none";
inputElement.focus();
markerAddress.setMap(null);
});

debouncedAutocomplete = debouncePromise(autocompleteAddress, 0);
}

function initMap(): void {
map = new woosmap.map.Map(document.getElementById("map") as HTMLElement, {
center: {
lat: 48.8534,
lng: 2.3488,
},
disableDefaultUI: true,
gestureHandling: "greedy",
zoom: 5,
styles: [
{
featureType: "poi",
stylers: [{ visibility: "off" }],
},
],
});
}

function handleAutocomplete(): void {
if (inputElement && suggestionsList) {
const input = inputElement.value;
input.replace('"', '\\"').replace(/^\s+|\s+$/g, "");

if (input !== "") {
debouncedAutocomplete(input)
.then(({ localities }) => displaySuggestions(localities))
.catch((error) =>
console.error("Error autocomplete localities:", error),
);
} else {
suggestionsList.style.display = "none";
clearSearchBtn.style.display = "none";
}
}
}

function displaySuggestions(
localitiesPredictions: woosmap.map.localities.LocalitiesPredictions[],
) {
if (inputElement && suggestionsList) {
suggestionsList.innerHTML = "";
if (localitiesPredictions.length > 0) {
localitiesPredictions.forEach((locality) => {
const li = document.createElement("li");
li.innerHTML = formatPredictionList(locality) ?? "";
li.addEventListener("click", () => {
inputElement.value = locality.description ?? "";
requestDetailsAddress(locality.public_id);
suggestionsList.style.display = "none";
});
suggestionsList.appendChild(li);
});
suggestionsList.style.display = "block";
clearSearchBtn.style.display = "block";
} else {
suggestionsList.style.display = "none";
}
}
}

function formatPredictionList(
locality: woosmap.map.localities.LocalitiesPredictions,
): string {
const formattedName =
locality.matched_substrings && locality.matched_substrings.description
? bold_matched_substring(
locality.description as string,
locality.matched_substrings.description,
)
: locality.description;

const addresses = locality.has_addresses
? `<span class='light'>- view addresses</span>`
: "";
const predictionClass = locality.has_addresses
? `prediction-expandable`
: "no-viewpoint";

return `<div class="prediction ${predictionClass}">${formattedName} ${addresses}</div>`;
}

function bold_matched_substring(
string: string,
matched_substrings: woosmap.map.AutocompleteMatchedSubstring[],
) {
matched_substrings = matched_substrings.reverse();
for (const substring of matched_substrings) {
const char = string.substring(
substring["offset"],
substring["offset"] + substring["length"],
);
string = `${string.substring(
0,
substring["offset"],
)}<span class='bold'>${char}</span>${string.substring(
substring["offset"] + substring["length"],
)}`;
}
return string;
}

function autocompleteAddress(
input: string,
): Promise<woosmap.map.localities.LocalitiesAutocompleteResponse> {
const args = {
key: woosmap_key,
input,
};
args["components"] = countryRestrictions
.map((country) => `country:${country}`)
.join("|");
args["types"] = types.join("|");
return fetch(
`https://api.woosmap.com/localities/autocomplete/?${buildQueryString(args)}`,
).then((response) => response.json());
}

function buildQueryString(params: object) {
const queryStringParts = [];

for (const key in params) {
if (params[key]) {
const value = params[key];
queryStringParts.push(
`${encodeURIComponent(key)}=${encodeURIComponent(value)}` as never,
);
}
}
return queryStringParts.join("&");
}

function createAddressMarker(
addressDetail: woosmap.map.localities.LocalitiesDetailsResult,
) {
if (markerAddress) {
markerAddress.setMap(null);
}
if (addressDetail && addressDetail.geometry) {
markerAddress = new woosmap.map.Marker({
position: addressDetail.geometry.location,
icon: {
url: "https://images.woosmap.com/marker.png",
scaledSize: {
height: 59,
width: 37,
},
},
});
markerAddress.setMap(map);
panMap(addressDetail);
}
}

function panMap(addressDetail: woosmap.map.localities.LocalitiesDetailsResult) {
let geometry;
if (addressDetail && addressDetail.geometry) {
geometry = addressDetail.geometry;
}
if (
addressDetail &&
addressDetail.geometry &&
addressDetail.geometry.viewport
) {
const { viewport } = geometry;
const bounds = {
east: viewport.northeast.lng,
south: viewport.southwest.lat,
north: viewport.northeast.lat,
west: viewport.southwest.lng,
};
map.fitBounds(bounds);
map.panTo(addressDetail.geometry.location);
} else {
let zoom = 17;
if (addressDetail.types[0] === "address") {
zoom = 18;
}
map.setZoom(zoom);
map.panTo(geometry.location);
}
}

function requestDetailsAddress(public_id: string) {
getLocalitiesDetails(public_id).then(
(addressDetails: woosmap.map.localities.LocalitiesDetailsResponse) => {
if (addressDetails.result["addresses"]) {
populateAddressList(addressDetails.result["addresses"]);
}
if (addressDetails) {
createAddressMarker(addressDetails.result);
}
},
);
}

function populateAddressList(addresses) {
if (inputElement && suggestionsList) {
suggestionsList.innerHTML = "";
if (addresses["list"].length > 0) {
addresses["list"].forEach((address) => {
const li = document.createElement("li");
li.innerHTML = formatPredictionList(address) ?? "";
li.addEventListener("click", () => {
inputElement.value = address.description ?? "";
requestDetailsAddress(address.public_id);
});
suggestionsList.appendChild(li);
});
suggestionsList.style.display = "block";
clearSearchBtn.style.display = "block";
}
}
}

function getLocalitiesDetails(
publicId: string,
): Promise<woosmap.map.localities.LocalitiesDetailsResponse> {
const args = {
public_id: publicId,
key: woosmap_key,
};
return fetch(
`https://api.woosmap.com/localities/details/?${buildQueryString(args)}`,
).then((response) => response.json());
}

type DebouncePromiseFunction<T, Args extends any[]> = (
...args: Args
) => Promise<T>;

function debouncePromise<T, Args extends any[]>(
fn: (...args: Args) => Promise<T>,
delay: number,
): DebouncePromiseFunction<T, Args> {
let timeoutId: ReturnType<typeof setTimeout> | null = null;
let latestResolve: ((value: T | PromiseLike<T>) => void) | null = null;
let latestReject: ((reason?: any) => void) | null = null;

return function (...args: Args): Promise<T> {
return new Promise<T>((resolve, reject) => {
if (timeoutId !== null) {
clearTimeout(timeoutId);
}
latestResolve = resolve;
latestReject = reject;
timeoutId = setTimeout(() => {
fn(...args)
.then((result) => {
if (latestResolve === resolve && latestReject === reject) {
resolve(result);
}
})
.catch((error) => {
if (latestResolve === resolve && latestReject === reject) {
reject(error);
}
});
}, delay);
});
};
}

document.addEventListener("DOMContentLoaded", () => {
inputElement = document.getElementById(
"autocomplete-input",
) as HTMLInputElement;
suggestionsList = document.getElementById(
"suggestions-list",
) as HTMLUListElement;
clearSearchBtn = document.getElementsByClassName(
"clear-searchButton",
)[0] as HTMLButtonElement;
init();
});

declare global {
interface Window {
initMap: () => void;
}
}
window.initMap = initMap;
// [END woosmap_localities_api_details_postalcodes]

export {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"title": "Localities Api - Details (Autocomplete on postal codes with addresses)",
"description": "Get details of localities using autocomplete on postal codes only. (only available in the UK).",
"tag": "localities_api_details_postalcodes",
"name": "localities-api-details-postalcodes",
"callback": "initMap",
"pagination": {
"data": "mode",
"size": 1,
"alias": "mode"
},
"permalink": "samples/{{ page.fileSlug }}/{{mode}}/{% if mode == 'jsfiddle' %}demo{% else %}index{% endif %}.{{ page.outputFileExtension }}"
}
29 changes: 29 additions & 0 deletions samples/localities-api-details-postalcodes/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@use 'sass:meta'; // To enable @use via meta.load-css and keep comments in order

/* [START woosmap_localities_api_details_postalcodes] */
@include meta.load-css("../../shared/scss/_default.scss");
@include meta.load-css("../../shared/scss/_autocomplete_input.scss");
@include meta.load-css("../../shared/scss/_autocomplete_list.scss");

#app {
height: 100%;
font-size: 13px;
}
.bold {
font-weight: 700;
}
.light {
font-size: 12px;
color: #3d5afe;
font-weight: 600;
}
.prediction-expandable {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='5' height='9' viewBox='0 0 5 9'%3E%3Cg fill='%23737373'%3E%3Cpath d='M0.714285714,8.57142857 C0.5,8.57142857 0.357142857,8.5 0.214285714,8.35714286 C-0.0714285714,8.07142857 -0.0714285714,7.64285714 0.214285714,7.35714286 L3.78571429,3.78571429 C4.07142857,3.5 4.5,3.5 4.78571429,3.78571429 C5.07142857,4.07142857 5.07142857,4.5 4.78571429,4.78571429 L1.21428571,8.35714286 C1.07142857,8.5 0.928571429,8.57142857 0.714285714,8.57142857 L0.714285714,8.57142857 Z'/%3E%3Cpath d='M4.28571429,5 C4.07142857,5 3.92857143,4.92857143 3.78571429,4.78571429 L0.214285714,1.21428571 C-0.0714285714,0.928571429 -0.0714285714,0.5 0.214285714,0.214285714 C0.5,-0.0714285714 0.928571429,-0.0714285714 1.21428571,0.214285714 L4.78571429,3.78571429 C5.07142857,4.07142857 5.07142857,4.5 4.78571429,4.78571429 C4.64285714,4.92857143 4.5,5 4.28571429,5 L4.28571429,5 Z'/%3E%3C/g%3E%3C/svg%3E%0A");
background-repeat: no-repeat;
background-position: right 5px center;
padding-right: 18px;
}
#suggestions-list{
font-size: 13px;
}
/* [END woosmap_localities_api_details_postalcodes] */
Loading

0 comments on commit 018310a

Please sign in to comment.