diff --git a/mo/web/__init__.py b/mo/web/__init__.py index 782dba3ebd9d1dc16d5b6fda3839e897eca294c3..05998d0ffb1467c60ddba3bd76c96e1894886ef9 100644 --- a/mo/web/__init__.py +++ b/mo/web/__init__.py @@ -116,6 +116,7 @@ app.assets.add_assets([ 'mo.css', 'js/news-reloader.js', 'js/osmo.js', + 'js/autocomplete.js' ]) diff --git a/static/js/autocomplete.js b/static/js/autocomplete.js new file mode 100644 index 0000000000000000000000000000000000000000..7148dc8813b6e08b97796eb5f3cbcac3c5e7e664 --- /dev/null +++ b/static/js/autocomplete.js @@ -0,0 +1,111 @@ +// Adapted from https://www.w3schools.com/howto/howto_js_autocomplete.asp +function autocomplete(inp, arr, callback=null, max=10) { + var currentFocus; + var listCount; + + function do_autocomplete(e) { + var a, b, i, val = this.value; + closeAllLists(); + + currentFocus = -1; + a = document.createElement("DIV"); + a.setAttribute("id", this.id + "autocomplete-list"); + a.setAttribute("class", "autocomplete-items"); + this.parentNode.appendChild(a); + + listCount = 0; + for (i = 0; i < arr.length; i++) { + var key, text; + if (Array.isArray(arr[i])) { + key = arr[i][0]; text = arr[i][1]; + } else { + key = arr[i]; text = arr[i]; + } + + /*check if the item starts with the same letters as the text field value:*/ + //if (text.substr(0, val.length).toUpperCase() == val.toUpperCase()) { + index = text.toUpperCase().indexOf(val.toUpperCase()); + if (index != -1) { + listCount++; + b = document.createElement("DIV"); + b.innerHTML = text.substr(0, index) + "<strong>" + text.substr(index, val.length) + "</strong>" + text.substr(index + val.length) + b.innerHTML += "<input type='hidden' value='" + key + "'>"; + b.innerHTML += "<input type='hidden' value='" + text + "'>"; + b.addEventListener("click", function(e) { + key = this.getElementsByTagName("input")[0].value; + text = this.getElementsByTagName("input")[1].value; + inp.value = text; + if (callback) { + callback(key); + } + closeAllLists(); + }); + a.appendChild(b); + + } + if (listCount == max) break; + } + }; + + inp.addEventListener("input", do_autocomplete); + inp.addEventListener("focus", do_autocomplete); + + inp.addEventListener("keydown", function(e) { + var x = document.getElementById(this.id + "autocomplete-list"); + if (x) x = x.getElementsByTagName("div"); + if (e.keyCode == 40) { // arrow down + currentFocus++; + addActive(x); + } else if (e.keyCode == 38) { // arrow up + currentFocus--; + addActive(x); + } else if (e.keyCode == 13) { // enter + e.preventDefault(); // do not submit the form + if (currentFocus == -1 && listCount > 0) { + currentFocus++; + } + if (currentFocus > -1) { + if (x) x[currentFocus].click(); // simulate a click on the "active" item + } + + } else if (e.keyCode == 27) { // escape + inp.blur(); + closeAllLists(); + } + e.stopPropagation(); + }); + + function addActive(x) { + if (!x) return false; + removeActive(x); + if (currentFocus >= x.length) currentFocus = 0; + if (currentFocus < 0) currentFocus = (x.length - 1); + x[currentFocus].classList.add("autocomplete-active"); + } + function removeActive(x) { + 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 && (!elmnt || elmnt.parentNode != x[i].parentNode)) { + x[i].parentNode.removeChild(x[i]); + } + } + } + // close all autocomplete lists when clicked elsewhere + document.addEventListener("click", function (e) { + closeAllLists(e.target); + }); +} + + +function closeAllAutocomplete() { + var x = document.getElementsByClassName("autocomplete-items"); + for (var i = 0; i < x.length; i++) { + x[i].parentNode.removeChild(x[i]); + } +} diff --git a/static/mo.css b/static/mo.css index 7dcf41627fcf49589e0437c14bdfd8833da9d03f..188831d0cb8bdb02ccd0de25948156085c73e55f 100644 --- a/static/mo.css +++ b/static/mo.css @@ -408,3 +408,35 @@ div.message .msg-date { font-style: italic; color: #777; } + +/* Autocomplete for inputs */ +.autocomplete { + position: relative; + display: inline-block; +} + +.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; + margin: 0px 15px; +} +.autocomplete-items div { + padding: 5px; + cursor: pointer; + background-color: #fff; + border-bottom: 1px solid #d4d4d4; +} +.autocomplete-items div:hover { + background-color: #e9e9e9; +} +.autocomplete-active { + background-color: DodgerBlue !important; + color: #ffffff; +}