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
|
/**
* Autocomplete.
*/
const cache = {};
let inputField, originalTerm;
function removeParent() {
const parent = document.querySelector('.autocomplete');
if (parent) parent.parentNode.removeChild(parent);
}
function removeSelected() {
const selected = document.querySelector('.autocomplete__item--selected');
if (selected) selected.classList.remove('autocomplete__item--selected');
}
function changeSelected(firstOrLast, current, sibling) {
if (current && sibling) { // if the currently selected item has a sibling, move selection to it
current.classList.remove('autocomplete__item--selected');
sibling.classList.add('autocomplete__item--selected');
}
else if (current) { // if the next keypress will take the user outside the list, restore the unautocompleted term
inputField.value = originalTerm;
removeSelected();
}
else if (firstOrLast) { // if no item in the list is selected, select the first or last
firstOrLast.classList.add('autocomplete__item--selected');
}
}
function keydownHandler(event) {
const selected = document.querySelector('.autocomplete__item--selected'),
firstItem = document.querySelector('.autocomplete__item:first-of-type'),
lastItem = document.querySelector('.autocomplete__item:last-of-type');
if (event.keyCode === 38) changeSelected(lastItem, selected, selected && selected.previousSibling); // ArrowUp
if (event.keyCode === 40) changeSelected(firstItem, selected, selected && selected.nextSibling); // ArrowDown
if (event.keyCode === 13 || event.keyCode === 27 || event.keyCode === 188) removeParent(); // Enter || Esc || Comma
if (event.keyCode === 38 || event.keyCode === 40) { // ArrowUp || ArrowDown
const newSelected = document.querySelector('.autocomplete__item--selected');
if (newSelected) inputField.value = newSelected.dataset.value;
event.preventDefault();
}
}
function createItem(list, suggestion) {
const item = document.createElement('li');
item.className = 'autocomplete__item';
item.textContent = suggestion.label;
item.dataset.value = suggestion.value;
item.addEventListener('mouseover', () => {
removeSelected();
item.classList.add('autocomplete__item--selected');
inputField.value = item.dataset.value;
});
item.addEventListener('mouseout', () => {
inputField.value = originalTerm;
removeSelected();
});
item.addEventListener('click', () => {
inputField.dispatchEvent(
new CustomEvent('autocomplete', {
detail: {
type: 'click',
label: suggestion.label,
value: suggestion.value,
}
})
);
});
list.appendChild(item);
}
function createList(suggestions) {
const parent = document.querySelector('.autocomplete'),
list = document.createElement('ul');
list.className = 'autocomplete__list';
suggestions.forEach(suggestion => createItem(list, suggestion));
parent.appendChild(list);
}
function createParent() {
const parent = document.createElement('div');
parent.className = 'autocomplete';
// Position the parent below the inputfield
parent.style.position = 'absolute';
parent.style.left = `${inputField.offsetLeft}px`;
// Take the inputfield offset, add its height and subtract the amount by which the parent element has scrolled
parent.style.top = `${inputField.offsetTop + inputField.offsetHeight - inputField.parentNode.scrollTop}px`;
// We append the parent at the end of body
document.body.appendChild(parent);
}
function showAutocomplete(suggestions, targetInput) {
// Remove old autocomplete suggestions
removeParent();
// Save suggestions in cache
cache[targetInput.value] = suggestions;
// If the input target is not empty, still visible, and suggestions were found
if (targetInput.value && targetInput.style.display !== 'none' && suggestions.length) {
createParent();
createList(suggestions);
inputField.addEventListener('keydown', keydownHandler);
}
}
function getSuggestions() {
return fetch(inputField.dataset.acSource + inputField.value).then(response => response.json());
}
function listenAutocomplete() {
let timeout;
document.addEventListener('input', event => {
removeParent();
window.clearTimeout(timeout);
// Use a timeout to delay requests until the user has stopped typing
timeout = window.setTimeout(() => {
inputField = event.target;
originalTerm = inputField.value;
const {ac, acMinLength} = inputField.dataset;
if (ac && (inputField.value.length >= acMinLength)) {
if (cache[inputField.value]) {
showAutocomplete(cache[inputField.value], event.target);
}
else {
// inputField could get overwritten while the suggestions are being fetched - use event.target
getSuggestions().then(suggestions => showAutocomplete(suggestions, event.target));
}
}
}, 300);
});
// If there's a click outside the inputField, remove autocomplete
document.addEventListener('click', event => {
if (event.target && event.target !== inputField) removeParent();
});
}
export { listenAutocomplete };
|