aboutsummaryrefslogtreecommitdiff
path: root/cockpit/js/selection.js
blob: a1da2440c25ad29df0e818869f2decefaaa7b30f (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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/**
 * 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 candidates = await fetchCandidates();

	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,
					"<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();
		}
	});
}

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 };