github.com/kyleu/dbaudit@v0.0.2-0.20240321155047-ff2f2c940496/client/src/autocomplete.ts (about) 1 // Content managed by Project Forge, see [projectforge.md] for details. 2 function debounce(callback: (...args: unknown[]) => void, wait: number) { 3 let timeoutId = 0; 4 return (...args: unknown[]) => { 5 if (timeoutId !== 0) { 6 window.clearTimeout(timeoutId); 7 } 8 timeoutId = window.setTimeout(() => { 9 callback(null, ...args); 10 }, wait); 11 }; 12 } 13 14 function autocomplete(el: HTMLInputElement, url: string, field: string, title: (x: unknown) => string, val: (x: unknown) => string) { 15 if (!el) { 16 return; 17 } 18 const listId = el.id + "-list"; 19 const list = document.createElement("datalist"); 20 21 const loadingOpt = document.createElement("option"); 22 loadingOpt.value = ""; 23 loadingOpt.innerText = "Loading..."; 24 list.appendChild(loadingOpt); 25 26 list.id = listId; 27 el.parentElement?.prepend(list); 28 29 el.setAttribute("autocomplete", "off"); 30 el.setAttribute("list", listId); 31 32 const cache: { 33 [_: string]: { 34 url: string; 35 complete: boolean; 36 data: unknown[]; 37 frag: DocumentFragment; 38 } 39 } = {}; 40 let lastQuery = ""; 41 42 function getURL(q: string): string { 43 const dest = url; 44 if (dest.includes("?")) { 45 return dest + "&t=json&" + field + "=" + encodeURIComponent(q); 46 } 47 return dest + "?t=json&" + field + "=" + encodeURIComponent(q); 48 } 49 50 function datalistUpdate(q: string) { 51 const c = cache[q]; 52 if (!c || !c.frag) { 53 return; 54 } 55 lastQuery = q; 56 list.replaceChildren(c.frag.cloneNode(true)); 57 } 58 59 function f() { 60 const q = el.value; 61 if (q.length === 0) { 62 return; 63 } 64 const dest = getURL(q); 65 let proceed: boolean = !q || !lastQuery; 66 if (!proceed) { 67 const l = cache[lastQuery]; 68 if (l) { 69 proceed = !l.data.find((d) => q === val(d)); 70 } 71 } 72 if (!proceed) { 73 return; 74 } 75 if (cache[q] && cache[q].url === dest) { 76 datalistUpdate(q); 77 return; 78 } 79 80 fetch(dest, {credentials: "include"}).then((res) => res.json()).then((data) => { 81 if (!data) { 82 return; 83 } 84 const arr = Array.isArray(data) ? data : [data]; 85 const frag = document.createDocumentFragment(); 86 let optMax = 10; 87 for (let d = 0; d < arr.length && optMax > 0; d++) { 88 const v = val(arr[d]); 89 const t = title(arr[d]); 90 if (v) { 91 const option = document.createElement("option"); 92 option.value = v; 93 option.innerText = t; 94 frag.appendChild(option); 95 optMax--; 96 } 97 } 98 cache[q] = {url: dest, data: arr, frag: frag, complete: false}; 99 datalistUpdate(q); 100 }); 101 } 102 103 el.oninput = debounce(f, 250); 104 console.log("managing [" + el.id + "] autocomplete"); 105 } 106 107 export function autocompleteInit() { 108 return autocomplete; 109 }