repos

blog.bythewood.me-wagtail

mirror archived upstream

Self-hostable blog for developers built on Wagtail and Django, with webpack, Bootstrap, and CodeMirror.

blogbootstrapdjangodockerhandcodedpythonself-hostedwagtailwebpack

3.3 KB · 80 lines · JavaScript Raw History
 1/**
 2 * Search.js
 3 *
 4 * Provides live search for any "id_search" field using the URL
 5 * /search/live/?q=<query>
 6 */
 7
 8document.addEventListener("DOMContentLoaded", function () {
 9  const searchEl = document.getElementById("id_search");
10  if (searchEl) {
11    searchEl.addEventListener("keyup", function (e) {
12      const query = searchEl.value;
13      if (query.length > 0) {
14        const url = "/search/live/?q=" + query;
15        fetch(url)
16          .then((response) => response.json())
17          .then((data) => {
18            // the results contain 'title', 'description', 'url'
19            // add a new el to the parent of searchEl for the results
20            // clear the previous el if it exists
21            let resultsEl = searchEl.parentElement.querySelector("#id_search_results");
22            if (resultsEl) {
23              searchEl.parentElement.removeChild(resultsEl);
24            }
25            resultsEl = document.createElement("div");
26            resultsEl.id = "id_search_results";
27            searchEl.parentElement.appendChild(resultsEl);
28            // add a new ul to the resultsEl
29            const resultsDiv = document.createElement("ul");
30            resultsDiv.classList.add("list-group", "rounded-bottom", "position-absolute", "mt-2");
31            resultsDiv.style.zIndex = "1100"; // above everything else bootstrap
32            resultsDiv.style.width = searchEl.offsetWidth + "px";
33            resultsEl.appendChild(resultsDiv);
34            // add a new li for each result
35            data.forEach((result) => {
36              const a = document.createElement("a");
37              a.classList.add("list-group-item", "list-group-item-action");
38              // strong the part of the title that matches the query
39              const title = result.title.replace(
40                new RegExp(query, "gi"),
41                "<strong>" + query + "</strong>"
42              );
43              const description = result.description.replace(
44                new RegExp(query, "gi"),
45                "<strong class='fw-bolder'>" + query + "</strong>"
46              );
47              // add the title and the description, the search_descrption
48              // should be in a span and muted
49              a.innerHTML = "<span class='fw-bold'>" + title + "</span>" + "<br><span class='text-muted'>" + description + "</span>";
50              a.href = result.url;
51              resultsDiv.appendChild(a);
52            });
53          });
54      }
55    });
56    // if the user clears the search field, remove the results
57    searchEl.addEventListener("keyup", function (e) {
58      if (searchEl.value.length === 0) {
59        const resultsEl = searchEl.parentElement.querySelector("#id_search_results");
60        if (resultsEl) {
61          searchEl.parentElement.removeChild(resultsEl);
62        }
63      }
64    });
65    // if we are not focusing inside the search field, remove the results
66    searchEl.addEventListener("blur", function (e) {
67      // unless we are clicking on the results
68      const searchResults = searchEl.parentElement.querySelector("#id_search_results");
69      if (searchResults) {
70        if (!searchResults.contains(e.relatedTarget)) {
71          const resultsEl = searchEl.parentElement.querySelector("#id_search_results");
72          if (resultsEl) {
73            searchEl.parentElement.removeChild(resultsEl);
74          }
75        }
76      }
77    });
78  }
79});