/** * Initialises a selection based on existing HTML elements. * For HTML elements are required: * - A text input with id $idPrefix * - A list with id ${idPrefix}-list * - An element shown if no results are available with id ${idPrefix}-noresults * - A button to clear the input with id ${idPrefix}-clear * * @param {string} idPrefix - The id prefix of the four HTML elements. * @param {async function} fetchCandidates - The async function to retrieve the selectable candidates. * @param {function} fmtCandidate - The function to format the displayed HTML for each candidate. * @param {function} onSelection - The function to call when a candidate is selected. */ async function initSelection(idPrefix, fetchCandidates, fmtCandidate, onSelection) { const sel = document.getElementById(idPrefix); const list = document.getElementById(`${idPrefix}-list`); const noResults = document.getElementById(`${idPrefix}-noresults`); const clearBtn = document.getElementById(`${idPrefix}-clear`); const loader = document.getElementById(`${idPrefix}-loader`); loader.style.display = "unset"; const candidates = await fetchCandidates(); loader.style.display = "none"; let items = []; let currentFocus = -1; let visibleItems = []; function setActive() { visibleItems.forEach(item => item.classList.remove("active")); if (currentFocus >= 0 && visibleItems[currentFocus]) { visibleItems[currentFocus].classList.add("active"); } } for (const candidate of candidates) { let item = document.createElement("li"); item.candidate = candidate; item.innerHTML = fmtCandidate(candidate); item.style.display = 'none'; item.addEventListener('click', function() { sel.value = ""; sel.dispatchEvent(new Event('keyup')); onSelection(candidate); }); list.appendChild(item); items.push(item); } sel.addEventListener('keyup', function(e) { if ((["ArrowDown", "ArrowUp", "Enter"]).includes(e.key)) return; const filter = sel.value.toLowerCase(); visibleItems = []; for (const item of items) { const itemText = item.textContent.trim().toLowerCase(); if (itemText.indexOf(filter) > -1 && filter !== "") { item.style.display = ""; visibleItems.push(item); // Highlight matched text const regex = new RegExp(`(${filter})`, 'gi'); item.innerHTML = item.textContent.replace( regex, "$1" ); } else { item.style.display = "none"; } } // Show the clear button if there is an input clearBtn.style.display = sel.value ? "block" : "none"; // Select the first or none if there are no items visible currentFocus = visibleItems.length ? 0 : -1; setActive(visibleItems); noResults.style.display = visibleItems.length || filter === "" ? "none" : "block"; }); clearBtn.addEventListener('click', function() { sel.value = ""; sel.dispatchEvent(new Event('keyup')); // reset list sel.focus(); }); sel.addEventListener('keydown', function(e) { if (e.key === "ArrowDown" || e.key === "ArrowUp") { if(e.key === "ArrowDown") currentFocus = (currentFocus + 1) % visibleItems.length; else currentFocus = (currentFocus - 1 + visibleItems.length) % visibleItems.length; setActive(); e.preventDefault(); } else if (e.key === "Enter") { if (currentFocus > -1 && visibleItems[currentFocus]) { const visibleItem = visibleItems[currentFocus]; sel.value = ""; sel.dispatchEvent(new Event('keyup')); onSelection(visibleItem.candidate); } e.preventDefault(); } }); } function pointClickHandler(color) { const textareaDiv = document.getElementById("student-info-point-reason-container"); const textarea = document.getElementById("student-info-point-reason"); const submitBtn = document.getElementById("student-info-point-reason-submit"); // TODO(muhq): Prefill the textarea with common reasons switch (color) { case "green": textarea.style.background = "rgb(from green r g b / 20%)" textarea.value = ""; break; case "orange": textarea.style.background = "rgb(from orange r g b / 20%)" textarea.value = ""; break; case "red": textarea.style.background = "rgb(from red r g b / 20%)" textarea.value = ""; break; } textareaDiv.style.display = "block"; textarea.focus(); // Can not use string interpolation because of Moodle's firstname filter. const coach = firstname + " " + lastname; let submit = function() { let change; switch (color) { case "green": change = GraduationChange.newGreenPointChange(student, textarea.value, coach); break; case "orange": change = GraduationChange.newOrangePointChange(student, textarea.value, coach); break; case "red": change = GraduationChange.newRedPointChange(student, textarea.value, coach); break; default: alert("Invalid color"); break; } submitGraduationChange(student, change); textareaDiv.style.display = "none"; updateStudentInfo(); } // Use onEVENT to override any previous handler textarea.onkeydown = function(e) { if (e.shiftKey && e.key === "Enter") { submit(); } if (e.key === "Escape") { textareaDiv.style.display = "none"; } }; submitBtn.onclick = submit; } export { initSelection };