blob: bfaebc00b16a4c269f9ee89c152ea52d166e8bba (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
/**
* 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";
const 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) {
const 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,
"<u class='student-selection-list-highlight'>$1</u>"
);
} 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();
}
});
}
export { initSelection };
|