Skip to content

Commit

Permalink
feat: added stores search autocomplete api sample
Browse files Browse the repository at this point in the history
  • Loading branch information
gaelsimon committed Mar 14, 2024
1 parent f0fceab commit f870a79
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 0 deletions.
1 change: 1 addition & 0 deletions e2e/samples/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const AUTOCOMPLETE_WITHOUT_MAP_SAMPLES = [
"localities-js-widget",
"localities-js-widget-custom-desc",
"multisearch-js-api",
"stores-search-autocomplete-api",
];

const samples = fs
Expand Down
28 changes: 28 additions & 0 deletions samples/stores-search-autocomplete-api/index.njk
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{% extends '../../src/_includes/layout.njk' %}
{% block html %}
<!-- [START woosmap_{{ tag }}_div] -->
<div id="app">
<p class="title">Autocomplete input for Stores Search Autocomplete API</p>
<p class="note">
<em>Search stores based on localized names.</em>
</p>
<div class="search-container">
<div id="autocomplete-container">
{% svgIcon 'search.svg' %}
<input
type="text"
id="autocomplete-input"
placeholder="Search a store..."
autocomplete="off"
/>
<button aria-label="Clear" class="clear-searchButton" type="button">
{% svgIcon 'clear.svg' %}
</button>
<ul id="suggestions-list"></ul>
</div>
</div>
<pre id="response-container"></pre>
</div>
<!-- [END woosmap_{{ tag }}_div] -->
{% endblock %}
{% block api %}{% endblock %}
180 changes: 180 additions & 0 deletions samples/stores-search-autocomplete-api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// [START woosmap_stores_search_autocomplete_api]
const woosmap_key = "YOUR_API_KEY";
let debouncedAutocomplete: DebouncePromiseFunction<
woosmap.map.stores.StoresAutocompleteResponse,
[input: string]
>;
let inputElement: HTMLInputElement;
let suggestionsList: HTMLUListElement;
let clearSearchBtn: HTMLButtonElement;
let responseElement: HTMLElement;

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";
responseElement.style.display = "none";
inputElement.focus();
});

debouncedAutocomplete = debouncePromise(autocompleteStores, 0);
}

function handleAutocomplete(): void {
if (inputElement && suggestionsList) {
const input = inputElement.value;
input.replace('"', '\\"').replace(/^\s+|\s+$/g, "");
if (input !== "") {
debouncedAutocomplete(input)
.then((storePredictions) => displaySuggestions(storePredictions))
.catch((error) => console.error("Error autocomplete stores:", error));
} else {
suggestionsList.style.display = "none";
clearSearchBtn.style.display = "none";
}
}
}

function displaySuggestions({
predictions,
}: woosmap.map.stores.StoresAutocompleteResponse) {
if (inputElement && suggestionsList) {
suggestionsList.innerHTML = "";
if (predictions.length > 0) {
predictions.forEach((prediction) => {
const li = document.createElement("li");
li.innerHTML = formatPredictionList(prediction) ?? "";
li.addEventListener("click", () => {
inputElement.value = prediction.name ?? "";
requestDetailsStore(prediction.store_id);
suggestionsList.style.display = "none";
});
suggestionsList.appendChild(li);
});
suggestionsList.style.display = "block";
clearSearchBtn.style.display = "block";
} else {
suggestionsList.style.display = "none";
}
}
}

function formatPredictionList(
prediction: woosmap.map.stores.StorePrediction,
): string {
const predictionClass = "no-viewpoint";
let html = "";
html += `<div class="prediction ${predictionClass}">${prediction.highlighted}</div>`;
return html;
}

function displayStoresResponse(
storePrediction: woosmap.map.stores.StorePrediction,
) {
if (responseElement) {
responseElement.innerHTML = `<code>${JSON.stringify(storePrediction, null, 2)}</code>`;
responseElement.style.display = "block";
}
}

function requestDetailsStore(store_id: string) {
fetch(`https://api.woosmap.com/stores/${store_id}?key=${woosmap_key}`)
.then((response) => response.json())
.then((storePrediction) => displayStoresResponse(storePrediction))
.catch((error) => console.error("Error request details store:", error));
}

function autocompleteStores(
input: string,
): Promise<woosmap.map.stores.StoresAutocompleteResponse> {
const args = {
key: woosmap_key,
query: `localized:${input}`,
};

return fetch(
`https://api.woosmap.com/stores/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("&");
}

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;
responseElement = document.getElementById(
"response-container",
) as HTMLElement;
init();
});

// [END woosmap_stores_search_autocomplete_api]

export {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"title": "Stores Search Autocomplete API",
"description": "This example shows how to use the Stores Search Autocomplete API to search for stores based on their localized names.",
"category": "Services",
"WOOSMAP_PUBLIC_API_KEY": "woos-0c78592f-13ea-362b-aa07-ba4ba9ea3dae",
"tag": "stores_search_autocomplete_api",
"name": "stores-search-autocomplete-api",
"pagination": {
"data": "mode",
"size": 1,
"alias": "mode"
},
"permalink": "samples/{{ page.fileSlug }}/{{mode}}/{% if mode == 'jsfiddle' %}demo{% else %}index{% endif %}.{{ page.outputFileExtension }}"
}
9 changes: 9 additions & 0 deletions samples/stores-search-autocomplete-api/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@use 'sass:meta'; // To enable @use via meta.load-css and keep comments in order

/* [START woosmap_stores_search_autocomplete_api] */
@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");
@include meta.load-css("../../shared/scss/_autocomplete_without_map.scss");

/* [END woosmap_stores_search_autocomplete_api] */

0 comments on commit f870a79

Please sign in to comment.