diff --git a/app/__pycache__/main.cpython-313.pyc b/app/__pycache__/main.cpython-313.pyc index e86ac03..a5ecf3d 100644 Binary files a/app/__pycache__/main.cpython-313.pyc and b/app/__pycache__/main.cpython-313.pyc differ diff --git a/app/main.py b/app/main.py index 349fd29..98640a9 100644 --- a/app/main.py +++ b/app/main.py @@ -59,7 +59,9 @@ async def get_file(file_id: str): @app.get("/search/lva") -async def search_lva(searchterm: str, searchlim: int = 10) -> List[Dict[str, str]]: +async def search_lva( + searchterm: str, searchlim: int = 10 +) -> List[Dict[str, int | str]]: res = [] cur = db.cursor(dictionary=True) if await is_LVID(searchterm): @@ -69,10 +71,15 @@ async def search_lva(searchterm: str, searchlim: int = 10) -> List[Dict[str, str res = cur.fetchall() else: cur.execute( - "SELECT lvid,lvname FROM LVAs WHERE lvname LIKE ?", - ("%" + searchterm + "%",), + "SELECT id,lvid,lvname FROM LVAs WHERE lvname LIKE ?", + (searchterm + "%",), ) res = cur.fetchall() + cur.execute( + "SELECT id,lvid,lvname FROM LVAs WHERE lvname LIKE ?", + ("%" + searchterm + "%",), + ) + res = remove_duplicates(res + cur.fetchall()) if searchlim == 0: return res else: @@ -81,13 +88,14 @@ async def search_lva(searchterm: str, searchlim: int = 10) -> List[Dict[str, str @app.get("/search/prof") async def search_profs( - searchterm: str = "", lvid: str = "", searchlim: int = 10 + searchterm: str = "", lid: int | None = None, searchlim: int = 10 ) -> List[Dict[str, str | int]]: res = [] + zw = [] cur = db.cursor(dictionary=True) - if lvid != "": - cur.execute("SELECT id FROM LVAs WHERE LVId=?", (lvid,)) - lid = cur.fetchall()[0]["id"] + if lid is not None: + # cur.execute("SELECT id FROM LVAs WHERE LVId=?", (lvid,)) + # lid = cur.fetchall()[0]["id"] cur.execute( "SELECT Profs.id,Profs.name FROM Profs LEFT JOIN LPLink ON Profs.id=LPLink.pid WHERE name like ? AND lid=?", ("%" + searchterm + "%", lid), @@ -115,7 +123,7 @@ async def search_profs( ) # NOT FULLY TESTED DUE TO INCOMPLETE DATABASE DUE TO INACCEPTABLE FOLDERSTRUCTURE async def search_subcats( searchterm: str = "", - lvid: str = "", + lid: int | None = None, pid: int | None = None, cat: int | None = None, searchlim: int = 10, @@ -123,16 +131,16 @@ async def search_subcats( res = [] rest = [] cur = db.cursor(dictionary=True) - if not (lvid == "" or pid is None or cat is None): # Rest is available - cur.execute("SELECT id FROM LVAs WHERE LVId=?", (lvid,)) - lid = cur.fetchall()[0]["id"] + if not (lid is None or pid is None or cat is None): # Rest is available + # cur.execute("SELECT id FROM LVAs WHERE LVId=?", (lvid,)) + # lid = cur.fetchall()[0]["id"] cur.execute( "SELECT id,name FROM SubCats WHERE lid=? AND pid=? AND cat=?", (lid, pid, cat), ) rest = cur.fetchall() if searchterm != "": # searchterm is available - if not (lvid == "" or pid is None or cat is None): + if not (lid is None or pid is None or cat is None): cur.execute( "SELECT id,name FROM SubCats WHERE lid=? AND pid=? AND cat=? AND name LIKE ?", (lid, pid, cat, "%" + searchterm + "%"), diff --git a/index.html b/index.html index 3c9fb7e..8b93d81 100644 --- a/index.html +++ b/index.html @@ -6,6 +6,7 @@ + @@ -27,10 +28,16 @@
-
+
+
+
+
-
- +
+
+
+
+

@@ -48,6 +55,12 @@

+ +
+ +
+
+

diff --git a/static/autocomplete.js b/static/autocomplete.js new file mode 100644 index 0000000..c0fb9c3 --- /dev/null +++ b/static/autocomplete.js @@ -0,0 +1,165 @@ +var url = "http://127.0.0.1:8000/search/"; +var lid = null; +var pid = null; +var activeAutocompletion = null; +/*Things I've stolen from https://www.w3schools.com/howto/howto_js_autocomplete.asp*/ +function autocomplete(inp, type) { + /*the autocomplete function takes two arguments, + the text field element and an array of possible autocompleted values:*/ + var currentFocus; + /*execute a function when someone writes in the text field:*/ + inp.addEventListener("focus", updateAutocomplete); + inp.addEventListener("input", updateAutocomplete); + async function updateAutocomplete() { + activeAutocompletion = type; + var a, + b, + i, + apirq, + iname, + val = this.value; + /*close any already open lists of autocompleted values*/ + closeAllLists(); + if (!val && type === "lva") { + return false; + } + if (type === "prof" && lid !== null) { + apirq = + url + type + "?searchterm=" + val + "&lid=" + lid + "&searchlim=10"; + } else if (type === "subcat" && lid !== null && pid !== null) { + apirq = + url + + type + + "?searchterm=" + + val + + "&lid=" + + lid + + "&pid=" + + pid + + "&cat=" + + document.getElementById("submitform").elements["stype"].value + + "&searchlim=10"; + } else { + apirq = url + type + "?searchterm=" + val + "&searchlim=10"; + } + const response = await fetch(apirq); + currentFocus = -1; + /*create a DIV element that will contain the items (values):*/ + a = document.createElement("DIV"); + a.setAttribute("id", this.id + "autocomplete-list"); + a.setAttribute("class", "autocomplete-items"); + /*append the DIV element as a child of the autocomplete container:*/ + this.parentNode.appendChild(a); + /*for each item in the array...*/ + //await response; + if (response.ok) { + arr = await response.json(); + } else { + console.error("API call failed. Request:\n" + apirq); + return false; + } + for (i = 0; i < arr.length; i++) { + if (type === "lva") { + iname = arr[i]["lvid"] + " " + arr[i]["lvname"]; + } else { + iname = arr[i]["name"]; + } + console.log(iname); + /*create a DIV element for each matching element:*/ + b = document.createElement("DIV"); + /*make the matching letters bold:*/ + //b.innerHTML = "" + iname.substr(0, val.length) + ""; + b.innerHTML = iname; //.substr(val.length); + /*insert a input field that will hold the current array item's value:*/ + b.innerHTML += ""; + /*execute a function when someone clicks on the item value (DIV element):*/ + b.addEventListener("click", function(e) { + /*insert the value for the autocomplete text field:*/ + if (type === "lva") { + const idx = this.getElementsByTagName("input")[0].value; + inp.value = arr[idx]["lvid"] + " " + arr[idx]["lvname"]; + lid = arr[idx]["id"]; + } else if (type === "prof") { + const idx = this.getElementsByTagName("input")[0].value; + inp.value = arr[idx]["name"]; + pid = arr[idx]["id"]; + } else { + inp.value = arr[this.getElementsByTagName("input")[0].value]["name"]; + } + /*close the list of autocompleted values, + (or any other open lists of autocompleted values:*/ + closeAllLists(); + }); + a.appendChild(b); + } + /*Add Listener to block the main click listener that destroys the autocompletion*/ + inp.addEventListener("click", function(e) { + e.stopImmediatePropagation(); + if (activeAutocompletion != type) { + closeAllLists(e.target); + } + }); + } + /*execute a function presses a key on the keyboard:*/ + inp.addEventListener("keydown", function(e) { + var x = document.getElementById(this.id + "autocomplete-list"); + if (x) x = x.getElementsByTagName("div"); + if (e.keyCode == 40) { + /*If the arrow DOWN key is pressed, + increase the currentFocus variable:*/ + currentFocus++; + /*and and make the current item more visible:*/ + addActive(x); + } else if (e.keyCode == 38) { + //up + /*If the arrow UP key is pressed, + decrease the currentFocus variable:*/ + currentFocus--; + /*and and make the current item more visible:*/ + addActive(x); + } else if (e.keyCode == 13) { + /*If the ENTER key is pressed, prevent the form from being submitted,*/ + e.preventDefault(); + if (currentFocus > -1) { + /*and simulate a click on the "active" item:*/ + if (x) x[currentFocus].click(); + } + } + }); + function addActive(x) { + /*a function to classify an item as "active":*/ + if (!x) return false; + /*start by removing the "active" class on all items:*/ + removeActive(x); + if (currentFocus >= x.length) currentFocus = 0; + if (currentFocus < 0) currentFocus = x.length - 1; + /*add class "autocomplete-active":*/ + x[currentFocus].classList.add("autocomplete-active"); + } + function removeActive(x) { + /*a function to remove the "active" class from all autocomplete items:*/ + for (var i = 0; i < x.length; i++) { + x[i].classList.remove("autocomplete-active"); + } + } + function closeAllLists(elmnt) { + /*close all autocomplete lists in the document, + except the one passed as an argument:*/ + var x = document.getElementsByClassName("autocomplete-items"); + for (var i = 0; i < x.length; i++) { + if (elmnt != x[i] && elmnt != inp) { + x[i].parentNode.removeChild(x[i]); + } + } + } + /*execute a function when someone clicks in the document:*/ + document.addEventListener("click", function(e) { + closeAllLists(e.target); + }); +} +function init() { + autocomplete(document.getElementById("lva"), "lva"); + autocomplete(document.getElementById("prof"), "prof"); + autocomplete(document.getElementById("subcat"), "subcat"); +} +window.addEventListener("load", init); diff --git a/static/style.css b/static/style.css index 7cb4309..78d3e0b 100644 --- a/static/style.css +++ b/static/style.css @@ -7,6 +7,7 @@ body { body { overflow: hidden; background-color: slategrey; + font-family: Arial, Helvetica, sans-serif; } .right { @@ -72,3 +73,63 @@ span { left: 0; top: 0; } + +#submitdiv { + /*position: relative;*/ + width: 500px; +} + +/*Things I've stolen from https://www.w3schools.com/howto/howto_js_autocomplete.asp*/ +.autocomplete { + /*the container must be positioned relative:*/ + position: relative; + display: inline-block; + width: 300px; +} + +input { + border: 1px solid transparent; + background-color: #f1f1f1; + padding: 10px; + font-size: 16px; +} + +input[type="text"] { + background-color: #f1f1f1; + width: 100%; +} + +input[type="submit"] { + background-color: DodgerBlue; + color: #fff; +} + +.autocomplete-items { + position: absolute; + border: 1px solid #d4d4d4; + border-bottom: none; + border-top: none; + z-index: 99; + /*position the autocomplete items to be the same width as the container:*/ + top: 100%; + left: 0; + right: 0; +} + +.autocomplete-items div { + padding: 10px; + cursor: pointer; + background-color: #fff; + border-bottom: 1px solid #d4d4d4; +} + +.autocomplete-items div:hover { + /*when hovering an item:*/ + background-color: #e9e9e9; +} + +.autocomplete-active { + /*when navigating through the items using the arrow keys:*/ + background-color: DodgerBlue !important; + color: #ffffff; +}