forked from SolidOS/solid-ui
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathautocompletePicker.js
More file actions
256 lines (256 loc) · 11.2 KB
/
Copy pathautocompletePicker.js
File metadata and controls
256 lines (256 loc) · 11.2 KB
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/* Autocomplete Picker: Create and edit data using public data
**
** As the data source is passed as a parameter, all kinds of APIa and query services can be used
**
*/
import * as debug from '../../../debug';
import { style } from '../../../style';
import styleConstants from '../../../styleConstants';
import * as widgets from '../../../widgets';
import { store } from 'solid-logic-jss';
import { queryPublicDataByName, bindingToTerm, AUTOCOMPLETE_LIMIT } from './publicData';
import { filterByLanguage, getPreferredLanguages, defaultPreferredLanguages } from './language';
const AUTOCOMPLETE_THRESHOLD = 4; // don't check until this many characters typed
const AUTOCOMPLETE_ROWS = 20; // 20?
const AUTOCOMPLETE_ROWS_STRETCH = 40;
export function setVisible(element, visible) {
element.style.display = visible ? '' : 'none'; // Do not use visibility, it holds the real estate
}
// The core of the autocomplete UI
export async function renderAutoComplete(dom, acOptions, decoration, callback) {
function complain(message) {
const errorRow = table.appendChild(dom.createElement('tr'));
debug.log(message);
const err = new Error(message);
errorRow.appendChild(widgets.errorMessageBlock(dom, err, 'pink'));
// errorMessageBlock will log the stack to the console
style.setStyle(errorRow, 'autocompleteRowStyle');
errorRow.style.padding = '1em';
}
function finish(object, name) {
debug.log('Auto complete: finish! ' + object);
if (object.termType === 'Literal' && acOptions.queryParams.objectURIBase) {
object = store.sym(acOptions.queryParams.objectURIBase.value + object.value);
}
// remove(decoration.cancelButton)
// remove(decoration.acceptButton)
// remove(div)
clearList();
callback(object, name);
}
async function gotIt(object, name) {
if (decoration.acceptButton) {
decoration.acceptButton.disbaled = false;
setVisible(decoration.acceptButton, true); // now wait for confirmation
searchInput.value = name.value; // complete it
foundName = name;
foundObject = object;
debug.log('Auto complete: name: ' + name);
debug.log('Auto complete: waiting for accept ' + object);
clearList(); // This may be an option - nice and clean but does not allow change of mind
return;
}
setVisible(decoration.cancelButton, true);
finish(object, name);
}
async function acceptButtonHandler(_event) {
if (foundName && searchInput.value === foundName.value) { // still
finish(foundObject, foundName);
}
}
async function cancelButtonHandler(_event) {
debug.log('Auto complete: Canceled by user! ');
if (acOptions.permanent) {
initialize();
}
else {
if (div.parentNode) {
div.parentNode.removeChild(div);
}
}
}
function nameMatch(filter, candidate) {
const parts = filter.split(' '); // Each name part must be somewhere
for (let j = 0; j < parts.length; j++) {
const word = parts[j];
if (candidate.toLowerCase().indexOf(word) < 0)
return false;
}
return true;
}
function clearList() {
while (table.children.length > 1) {
table.removeChild(table.lastChild);
}
}
async function inputEventHHandler(_event) {
// console.log('@@ AC inputEventHHandler called')
setVisible(decoration.cancelButton, true); // only allow cancel when there is something to cancel
refreshList(); /// @@ debounqce does not work with jest
/*
if (runningTimeout) {
clearTimeout(runningTimeout)
}
runningTimeout = setTimeout(refreshList, AUTOCOMPLETE_DEBOUNCE_MS)
*/
}
async function loadBindingsAndFilterByLanguage(filter, languagePrefs) {
// console.log('@@ loadBindingsAndFilterByLanguage ' + filter)
let bindings;
try {
bindings = await queryPublicDataByName(filter, targetClass, languagePrefs || defaultPreferredLanguages, acOptions.queryParams);
}
catch (err) {
complain('Error querying db of organizations: ' + err);
inputEventHandlerLock = false;
return;
}
loadedEnough = bindings.length < AUTOCOMPLETE_LIMIT;
if (loadedEnough) {
lastFilter = filter;
}
else {
lastFilter = undefined;
}
clearList();
const slimmed = filterByLanguage(bindings, languagePrefs);
return slimmed;
}
function filterByName(filter, bindings) {
return bindings.filter(binding => nameMatch(filter, binding.name.value));
}
async function refreshList() {
// console.log('@@ refreshList called')
function rowForBinding(binding) {
const row = dom.createElement('tr');
style.setStyle(row, 'autocompleteRowStyle');
row.setAttribute('style', 'padding: 0.3em;');
row.style.color = allDisplayed ? '#080' : '#088'; // green means 'you should find it here'
row.textContent = binding.name.value;
const object = bindingToTerm(binding.subject);
const nameTerm = bindingToTerm(binding.name);
row.addEventListener('click', async (_event) => {
debug.log(' click row textContent: ' + row.textContent);
debug.log(' click name: ' + nameTerm.value);
if (object && nameTerm) {
gotIt(object, nameTerm);
}
});
return row;
} // rowForBinding
function compareBindingsByName(self, other) {
return other.name.value > self.name.value
? 1
: other.name.name < self.name.value ? -1 : 0;
}
if (inputEventHandlerLock) {
debug.log(`Ignoring "${searchInput.value}" because of lock `);
return;
}
debug.log(`Setting lock at "${searchInput.value}"`);
inputEventHandlerLock = true;
const languagePrefs = await getPreferredLanguages();
const filter = searchInput.value.trim().toLowerCase();
if (filter.length < AUTOCOMPLETE_THRESHOLD) { // too small
clearList();
// candidatesLoaded = false
numberOfRows = AUTOCOMPLETE_ROWS;
}
else {
if (!allDisplayed || !lastFilter || !filter.startsWith(lastFilter)) {
debug.log(` Querying database at "${filter}" cf last "${lastFilter}".`);
lastBindings = await loadBindingsAndFilterByLanguage(filter, languagePrefs); // freesh query
}
// Trim table as search gets tighter:
const slimmed = filterByName(filter, lastBindings);
if (loadedEnough && slimmed.length <= AUTOCOMPLETE_ROWS_STRETCH) {
numberOfRows = slimmed.length; // stretch if it means we get all items
}
allDisplayed = loadedEnough && slimmed.length <= numberOfRows;
debug.log(` Filter:"${filter}" lastBindings: ${lastBindings.length}, slimmed to ${slimmed.length}; rows: ${numberOfRows}, Enough? ${loadedEnough}, All displayed? ${allDisplayed}`);
const displayable = slimmed.slice(0, numberOfRows);
displayable.sort(compareBindingsByName);
clearList();
for (const binding of displayable) {
table.appendChild(rowForBinding(binding));
}
if (slimmed.length === 1) {
gotIt(bindingToTerm(slimmed[0].subject), bindingToTerm(slimmed[0].name));
}
} // else
inputEventHandlerLock = false;
} // refreshList
function initialize() {
if (acOptions.currentObject) { // If have existing value then jump into the endgame of the autocomplete
searchInput.value = acOptions.currentName ? acOptions.currentName.value : '??? wot no name for ' + acOptions.currentObject;
foundName = acOptions.currentName;
lastFilter = acOptions.currentName ? acOptions.currentName.value : undefined;
foundObject = acOptions.currentObject;
}
else {
searchInput.value = '';
lastFilter = undefined;
foundObject = undefined;
}
if (decoration.deleteButton) {
setVisible(decoration.deleteButton, !!acOptions.currentObject);
}
if (decoration.acceptButton) {
setVisible(decoration.acceptButton, false); // hide until input complete
}
if (decoration.editButton) {
setVisible(decoration.editButton, true);
}
if (decoration.cancelButton) {
setVisible(decoration.cancelButton, false); // only allow cancel when there is something to cancel
}
inputEventHandlerLock = false;
clearList();
} // initialiize
// const queryParams: QueryParameters = acOptions.queryParams
const targetClass = acOptions.targetClass;
if (!targetClass)
throw new Error('renderAutoComplete: missing targetClass');
// console.log(`renderAutoComplete: targetClass=${targetClass}` )
if (decoration.acceptButton) {
decoration.acceptButton.addEventListener('click', acceptButtonHandler, false);
}
if (decoration.cancelButton) {
decoration.cancelButton.addEventListener('click', cancelButtonHandler, false);
}
// var candidatesLoaded = false
let lastBindings;
let loadedEnough = false;
let inputEventHandlerLock = false;
let allDisplayed = false;
let lastFilter = undefined;
let numberOfRows = AUTOCOMPLETE_ROWS; // this gets slimmed down
const div = dom.createElement('div');
let foundName = undefined; // once found accepted string must match this
let foundObject = undefined;
const table = div.appendChild(dom.createElement('table'));
table.setAttribute('data-testid', 'autocomplete-table');
table.setAttribute('style', 'max-width: 30em; margin: 0.5em;');
const head = table.appendChild(dom.createElement('tr'));
style.setStyle(head, 'autocompleteRowStyle'); // textInputStyle or
const cell = head.appendChild(dom.createElement('td'));
const searchInput = cell.appendChild(dom.createElement('input'));
searchInput.setAttribute('type', 'text');
initialize();
const size = acOptions.size || styleConstants.textInputSize || 20;
searchInput.setAttribute('size', size);
searchInput.setAttribute('data-testid', 'autocomplete-input');
const searchInputStyle = style.textInputStyle || // searchInputStyle ?
'border: 0.1em solid #444; border-radius: 0.5em; width: 100%; font-size: 100%; padding: 0.1em 0.6em'; // @
searchInput.setAttribute('style', searchInputStyle);
searchInput.addEventListener('keyup', function (event) {
if (event.keyCode === 13) {
acceptButtonHandler(event);
}
}, false);
searchInput.addEventListener('input', inputEventHHandler);
// console.log('@@ renderAutoComplete returns ' + div.innerHTML)
return div;
} // renderAutoComplete
// ENDS
//# sourceMappingURL=autocompletePicker.js.map