Core

/client/src/autocomplete.ts (2.7 KB)

  1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
function debounce(callback: (...args: unknown[]) => void, wait: number) {
let timeoutId = 0;
return (...args: unknown[]) => {
if (timeoutId !== 0) {
window.clearTimeout(timeoutId);
}
timeoutId = window.setTimeout(() => {
callback(null, ...args);
}, wait);
};
}

function autocomplete(el: HTMLInputElement, url: string, field: string, title: (x: unknown) => string, val: (x: unknown) => string) {
if (!el) {
return;
}
const listId = el.id + "-list";
const list = document.createElement("datalist");

const loadingOpt = document.createElement("option");
loadingOpt.value = "";
loadingOpt.innerText = "Loading...";
list.appendChild(loadingOpt);

list.id = listId;
el.parentElement?.prepend(list);

el.setAttribute("autocomplete", "off");
el.setAttribute("list", listId);

const cache: {
[_: string]: {
url: string;
complete: boolean;
data: unknown[];
frag: DocumentFragment;
}
} = {};
let lastQuery = "";

function getURL(q: string): string {
const dest = url;
if (dest.includes("?")) {
return dest + "&t=json&" + field + "=" + encodeURIComponent(q);
}
return dest + "?t=json&" + field + "=" + encodeURIComponent(q);
}

function datalistUpdate(q: string) {
const c = cache[q];
if (!c || !c.frag) {
return;
}
lastQuery = q;
list.replaceChildren(c.frag.cloneNode(true));
}

function f() {
const q = el.value;
if (q.length === 0) {
return;
}
const dest = getURL(q);
let proceed: boolean = !q || !lastQuery;
if (!proceed) {
const l = cache[lastQuery];
if (l) {
proceed = !l.data.find((d) => q === val(d));
}
}
if (!proceed) {
return;
}
if (cache[q] && cache[q].url === dest) {
datalistUpdate(q);
return;
}

fetch(dest, {credentials: "include"}).then((res) => res.json()).then((data) => {
if (!data) {
return;
}
const arr = Array.isArray(data) ? data : [data];
const frag = document.createDocumentFragment();
let optMax = 10;
for (let d = 0; d < arr.length && optMax > 0; d++) {
const v = val(arr[d]);
const t = title(arr[d]);
if (v) {
const option = document.createElement("option");
option.value = v;
option.innerText = t;
frag.appendChild(option);
optMax--;
}
}
cache[q] = {url: dest, data: arr, frag: frag, complete: false};
datalistUpdate(q);
});
}

el.oninput = debounce(f, 250);
console.log("managing [" + el.id + "] autocomplete");
}

export function autocompleteInit() {
return autocomplete;
}