From 53ce566a4c145c6676b42164f60d09a3e44d5e67 Mon Sep 17 00:00:00 2001 From: Marcel Gansfusz Date: Sat, 22 Feb 2025 02:17:14 +0100 Subject: [PATCH] implemented autocompletion --- app/__pycache__/main.cpython-313.pyc | Bin 11634 -> 11503 bytes app/main.py | 32 ++++-- index.html | 19 ++- static/autocomplete.js | 165 +++++++++++++++++++++++++++ static/style.css | 61 ++++++++++ 5 files changed, 262 insertions(+), 15 deletions(-) create mode 100644 static/autocomplete.js diff --git a/app/__pycache__/main.cpython-313.pyc b/app/__pycache__/main.cpython-313.pyc index e86ac030a3d4c79277a9b9300e2484a08d6368b0..a5ecf3d0e768c027db301e055782967522933d8b 100644 GIT binary patch delta 1485 zcmah}T}+!*7(VAa-`BsdEv0|71=>Q(PpO0vigPG9KsFc?SV|RGjVY8(GD7id=aemx z`7_}{2fmttaS^9DxYIOVc;$tOdSg=LrZ!VC8WZ(mMV6QuqrRtr=#?iq&w0*!-gD0T zJ@5OSUq@~a8+Qx_9l|L83BEgBv4l@DrGX_=dZ+rs5hci0{EhCNXs%A&}k?s_#L zutj^|x=;;0!ZEcstmd}#g0QTnFF>8{eKrzdkg;1?Sc<^!x)3eQU|UzFKP~gScpLIT zkzA&#N7_uGO0J^UpjSR2H-)=Sc6APVC*t?Ob-976;0w7D8ckNn%ZKbe=-!Gw`8mQ* zWR~PLL^eQ3->+}a8cLRY8;0_X-UD|{r9m{97Fy7-!Zr-PjK1oQ#I(9Q8dBt4VK*n= zo_uF&!yDQ(HqSBm)8=?&yM|~g?j}um@^k)9Lf@kuZQ{)i2htgRLvlQ(j6KmXtt8Im z*yKcfjG^CtlaM@*ediJ-#7Dg3b<#kl7-W7)R?+0b#TTk&XJnV|Fq& zWQLc`je4=5(Brs5M@ovL(&eU;G&eCG6vIbk9Wd1sw8hUc!PJuaW9jU!dY5un}egDLlb^b}4$g-_hK z>2Nyj`GFNj(U8Ls$5P4+_r3^gzR7x?ECs8KDCamljgVvU#8^(1IG>t6pUR2Tv9sg* zJKFsgdIci>`*aHT=I~?sn!jIgy@51|zX|awm?xri?StXfn(xbEI1A96DmFbmJ{1p$ z%3Em2v59z6>4v{*TsmH4g-Wx5uQq1qY3Nhl<-4RpPWt+A3O8yO12#EpYlU ze>)Ujb_$mo!%bigxH1Y?ASa!fOvF-!NOR(7A~Cre*>T7O%5@*22ah><1%3vu<;vjl zOuk@iJ&Gegp&D2C3C_HLEZ??wLRPfyFhLOT!7FiQ?Jh_JOH^9@Tx#Bxo(uj={{|6E BW48bR delta 1729 zcma)6TX2&_6yE(W&HbLx8%aacTeu7`w17c^v{P<{7WxxfCE7`o6f%aS?4N=L7!)38 zhX=rgu{h$CQINjjbOv~0#20xa>O=e$r!)HCWkyorMV~w;2@0bR>}0<^d-gkwvZSg0+t3KWo26-AY*TMtzDe>Q|bgLR5_UqCWmJ-A;Xcy^y57 zxle@4I@-*`#+x+AuNrIlL9315GcM;Jo4j<4-!tu?tN6F3R^DlLP?>KrH}hlW)$VQZ zUmIbn?*sfo-XZfwGylRouq0fx*w2Rw7VnH$umm2MTc60H!C2B0oAX?HDt$J?W0uB2 zFVQdgQ?-Nbsg`aVy-vF=(x;o-LCl+1s>v9OA6z-kvSXT*K9WkrMIb&MG6<((KK_al z5+HtZiL4|UkrynjHYq|CF?5uW*a<=+63^;94$yVdE}>%XN9}^q{a%~iuE=bKen)>&$ZXjypFTc{ev$toj zKb7!r864Q<-??wI>L1$HJJ<{2_Qb|b46~#OsYE_irS66rsC=KTz=QS`%B;-R0E2ZR zbRld(0|!eU${tCMBqqkwsdy}xRD(Je#^db>n-I1lga9-#ol0mz{-~;hou7YTZ!isl zgj1!I-!^+!6?LXbb~<0wo6ec0Ob^^^@3`;izR|x@v^pw1G`*)_ZJLn^*49Z8So}Bp zMPa6ypLDcIJ*_?MeAcm={?5O1ytD|C4!KDvkQ-riH)d#b7a@G!Su2+tgWqy?SfA?z zJsj2v!>4%Ix!PH2rctoN#=@KUF=v}iKcpWHD28gM82C-+R+I6Wz2Mpt`nM=D6h(%j zNdGAc>w~D-P6SLn1J_7?1Gl)F`9oK|kngLCZ^ZlIBxrahs}K=kdlCN0uO_jink#3S zL8wH3?Ea$JcCK%#uV{b0=y0EpbC2h^xTdFT?xN?KNQZf-zC|ua%SY>% z%1LnG%G`zefW+^5n|16E>{*6C^eXaOAl~I&zDG1V*V^=x=okS71{%+?<48Nf8v^Og zQ54@sDBI?dBqp+PttLB>8=uH&(s=AZvTA{cG^I&L$6|3!Ita)n`Imw9#vERN@WY&; zWk6{75JVLpn!lVp){Ex|GG}c+F2-sFsa6vkA5UfyL5W=jg(jvFD%;8rw>KCEkdI*D zm)c{#a1-@lc3G1~v)OdT#Q`1)dX2Y%fzGQcj|IOUSr8nwX~tC|u-vRHcM_j>-WTsH_x1O} zh4A1LJbEk=p{|77QU?*_Gqvy5PWry4b@LlGE{Kh^sc3hV%Bw$6S-aMkSN>qR$MHl4 o2HsF#5 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; +}