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  }