Self-hostable blog for developers built on Wagtail and Django, with webpack, Bootstrap, and CodeMirror.
blogbootstrapdjangodockerhandcodedpythonself-hostedwagtailwebpack
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});