1 | /*! |
||
2 | * @package ElkArte Forum |
||
3 | * @copyright ElkArte Forum contributors |
||
4 | * @license BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file) |
||
5 | * |
||
6 | * This file contains code covered by: |
||
7 | * copyright: 2011 Simple Machines (http://www.simplemachines.org) |
||
8 | * |
||
9 | * @version 2.0 dev |
||
10 | */ |
||
11 | |||
12 | /** global: elk_session_var, elk_session_id, elk_scripturl */ |
||
13 | |||
14 | /** |
||
15 | * This file contains javascript associated with an auto suggest control. |
||
16 | */ |
||
17 | |||
18 | /** |
||
19 | * The auto suggest class, used to display a selection list of members |
||
20 | * |
||
21 | * @param {object} oOptions |
||
22 | */ |
||
23 | function elk_AutoSuggest (oOptions) |
||
24 | { |
||
25 | this.opt = oOptions; |
||
26 | |||
27 | // Store the handle to the text box. |
||
28 | this.oTextHandle = document.getElementById(this.opt.sControlId); |
||
29 | this.oRealTextHandle = null; |
||
30 | this.oSuggestDivHandle = null; |
||
31 | |||
32 | this.sLastSearch = ''; |
||
33 | this.sLastDirtySearch = ''; |
||
34 | this.oSelectedDiv = null; |
||
35 | this.aCache = []; |
||
36 | this.aDisplayData = []; |
||
37 | this.sRetrieveURL = this.opt.sRetrieveURL || '%scripturl%action=suggest;api=xml'; |
||
38 | |||
39 | // How many objects can we show at once? |
||
40 | this.iMaxDisplayQuantity = this.opt.iMaxDisplayQuantity || 12; |
||
41 | |||
42 | // How many characters shall we start searching on? |
||
43 | this.iMinimumSearchChars = this.opt.iMinimumSearchChars || 2; |
||
44 | |||
45 | // Should selected items be added to a list? |
||
46 | this.bItemList = this.opt.bItemList || false; |
||
47 | |||
48 | // Are there any items that should be added in advance? |
||
49 | this.aListItems = this.opt.aListItems || []; |
||
50 | |||
51 | this.sItemTemplate = this.opt.sItemTemplate || '<input type="hidden" name="%post_name%[]" value="%item_id%" /><a href="%item_href%" class="extern" onclick="window.open(this.href, \'_blank\'); return false;">%item_name%</a><span class="breaking_space"></span><i class="icon icon-small i-remove" title="%delete_text%"><s>%delete_text%</s></i>'; |
||
52 | this.sTextDeleteItem = this.opt.sTextDeleteItem || ''; |
||
53 | this.oCallback = {}; |
||
54 | this.bDoAutoAdd = false; |
||
55 | this.iItemCount = 0; |
||
56 | this.oHideTimer = null; |
||
57 | this.bPositionComplete = false; |
||
58 | |||
59 | this.oXmlRequestHandle = null; |
||
60 | |||
61 | // Just make sure the page is loaded before calling the init. |
||
62 | window.addEventListener('load', this.init.bind(this)); |
||
63 | } |
||
64 | |||
65 | /** |
||
66 | * Initialize our auto suggest object, adds events and containers to the element we monitor |
||
67 | */ |
||
68 | elk_AutoSuggest.prototype.init = function() { |
||
69 | // Create a div that'll contain the results later on. |
||
70 | this.oSuggestDivHandle = document.createElement('div'); |
||
71 | this.oSuggestDivHandle.className = 'auto_suggest_div'; |
||
72 | document.body.appendChild(this.oSuggestDivHandle); |
||
73 | |||
74 | // Create a backup text input. |
||
75 | this.oRealTextHandle = document.createElement('input'); |
||
76 | this.oRealTextHandle.type = 'hidden'; |
||
77 | this.oRealTextHandle.name = this.oTextHandle.name; |
||
78 | this.oRealTextHandle.value = this.oTextHandle.value; |
||
79 | this.oTextHandle.form.appendChild(this.oRealTextHandle); |
||
80 | |||
81 | // Disable autocomplete in any browser by obfuscating the name. |
||
82 | this.oTextHandle.name = 'dummy_' + Math.floor(Math.random() * 1000000); |
||
83 | this.oTextHandle.autocomplete = 'off'; |
||
84 | |||
85 | // Set up all the event monitoring |
||
86 | this.oTextHandle.onkeydown = this.handleKey.bind(this); |
||
87 | this.oTextHandle.onkeyup = debounce((function () {this.autoSuggestUpdate();}).bind(this), 150); |
||
88 | this.oTextHandle.onchange = debounce((function () {this.autoSuggestUpdate();}).bind(this), 150); |
||
89 | this.oTextHandle.onblur = this.autoSuggestHide.bind(this); |
||
90 | this.oTextHandle.onfocus = this.autoSuggestUpdate.bind(this); |
||
91 | |||
92 | // Adding items to a list, then we need a place to insert them |
||
93 | if (this.bItemList) |
||
94 | { |
||
95 | if ('sItemListContainerId' in this.opt) |
||
96 | { |
||
97 | this.oItemList = document.getElementById(this.opt.sItemListContainerId); |
||
98 | } |
||
99 | else |
||
100 | { |
||
101 | this.oItemList = document.createElement('div'); |
||
102 | this.oTextHandle.parentNode.insertBefore(this.oItemList, this.oTextHandle.nextSibling); |
||
103 | } |
||
104 | } |
||
105 | |||
106 | // Items provided to add to the top of the selection list? |
||
107 | if (this.aListItems.length > 0) |
||
108 | { |
||
109 | for (let i = 0, n = this.aListItems.length; i < n; i++) |
||
110 | { |
||
111 | this.addItemLink(this.aListItems[i].sItemId, this.aListItems[i].sItemName, false); |
||
112 | } |
||
113 | } |
||
114 | |||
115 | return true; |
||
116 | }; |
||
117 | |||
118 | /** |
||
119 | * Handle keypress events for the suggest controller |
||
120 | */ |
||
121 | elk_AutoSuggest.prototype.handleKey = function(oEvent) { |
||
122 | let iKeyPress = this.getKeyPress(oEvent); |
||
123 | |||
124 | switch (iKeyPress) |
||
125 | { |
||
126 | case 9: // Tab |
||
127 | return this.handleTabKey(); |
||
128 | case 13: // Enter |
||
129 | return this.handleEnterKey(); |
||
130 | case 38: // Up arrow |
||
131 | case 40: // Down arrow |
||
132 | return this.handleArrowKey(iKeyPress); |
||
133 | } |
||
134 | |||
135 | return true; |
||
136 | }; |
||
137 | |||
138 | /** |
||
139 | * Gets the key pressed from the event handler. |
||
140 | */ |
||
141 | elk_AutoSuggest.prototype.getKeyPress = function(oEvent) { |
||
142 | return 'which' in oEvent ? oEvent.which : oEvent.keyCode; |
||
143 | }; |
||
144 | |||
145 | /** |
||
146 | * Handles the tab key press event for the AutoSuggest component. |
||
147 | */ |
||
148 | elk_AutoSuggest.prototype.handleTabKey = function() { |
||
149 | if (this.aDisplayData.length > 0) |
||
150 | { |
||
151 | if (this.oSelectedDiv !== null) |
||
152 | { |
||
153 | this.itemClicked(this.oSelectedDiv); |
||
154 | } |
||
155 | else |
||
156 | { |
||
157 | this.handleSubmit(); |
||
158 | } |
||
159 | } |
||
160 | |||
161 | return true; |
||
162 | }; |
||
163 | |||
164 | /** |
||
165 | * Handles the enter key press event for the auto-suggest feature. |
||
166 | * Triggers the selectItem() method to select the highlighted item |
||
167 | * when the enter key is pressed. |
||
168 | */ |
||
169 | elk_AutoSuggest.prototype.handleEnterKey = function() { |
||
170 | if (this.aDisplayData.length > 0 && this.oSelectedDiv !== null) |
||
171 | { |
||
172 | this.itemClicked(this.oSelectedDiv); |
||
173 | |||
174 | // Do our best to stop it submitting the form! |
||
175 | return false; |
||
176 | } |
||
177 | |||
178 | return true; |
||
179 | }; |
||
180 | |||
181 | /** |
||
182 | * Handles the up/down arrow key events for the AutoSuggest component. |
||
183 | */ |
||
184 | elk_AutoSuggest.prototype.handleArrowKey = function(iKeyPress) { |
||
185 | if (this.aDisplayData.length && this.oSuggestDivHandle.style.visibility !== 'hidden') |
||
186 | { |
||
187 | // Loop through the display data trying to find our entry. |
||
188 | let bPrevHandle = false, |
||
189 | oToHighlight = null; |
||
190 | |||
191 | for (let i = 0; i < this.aDisplayData.length; i++) |
||
192 | { |
||
193 | // If we're going up and yet the top one was already selected don't go around. |
||
194 | if (this.oSelectedDiv !== null && this.oSelectedDiv === this.aDisplayData[i] && i === 0 && iKeyPress === 38) |
||
195 | { |
||
196 | oToHighlight = this.oSelectedDiv; |
||
197 | break; |
||
198 | } |
||
199 | |||
200 | // If nothing is selected, and we are going down then we select the first one. |
||
201 | if (this.oSelectedDiv === null && iKeyPress === 40) |
||
202 | { |
||
203 | oToHighlight = this.aDisplayData[i]; |
||
204 | break; |
||
205 | } |
||
206 | |||
207 | // If the previous handle was the actual previously selected one, and we're hitting down then this is the one we want. |
||
208 | if (bPrevHandle !== false && bPrevHandle === this.oSelectedDiv && iKeyPress === 40) |
||
209 | { |
||
210 | oToHighlight = this.aDisplayData[i]; |
||
211 | break; |
||
212 | } |
||
213 | |||
214 | // If we're going up and this is the previously selected one then we want the one before, if there was one. |
||
215 | if (bPrevHandle !== false && this.aDisplayData[i] === this.oSelectedDiv && iKeyPress === 38) |
||
216 | { |
||
217 | oToHighlight = bPrevHandle; |
||
218 | break; |
||
219 | } |
||
220 | |||
221 | // Make the previous handle this! |
||
222 | bPrevHandle = this.aDisplayData[i]; |
||
223 | } |
||
224 | |||
225 | // If we don't have one to highlight by now then it must be the last one that we're after. |
||
226 | if (oToHighlight === null) |
||
227 | { |
||
228 | oToHighlight = bPrevHandle; |
||
229 | } |
||
230 | |||
231 | // Remove any old highlighting. |
||
232 | if (this.oSelectedDiv !== null) |
||
233 | { |
||
234 | this.itemMouseOut(this.oSelectedDiv); |
||
235 | } |
||
236 | |||
237 | // Mark what the selected div now is. |
||
238 | this.oSelectedDiv = oToHighlight; |
||
239 | this.itemMouseOver(this.oSelectedDiv); |
||
240 | } |
||
241 | |||
242 | return true; |
||
243 | }; |
||
244 | |||
245 | /** |
||
246 | * Handles the mouse over event for an item in the auto suggest menu. |
||
247 | */ |
||
248 | elk_AutoSuggest.prototype.itemMouseOver = function(oCurElement) { |
||
249 | this.oSelectedDiv = oCurElement; |
||
250 | oCurElement.className = 'auto_suggest_item_hover'; |
||
251 | }; |
||
252 | |||
253 | /** |
||
254 | * Handle the mouse out event on an item in the auto suggest dropdown. |
||
255 | */ |
||
256 | elk_AutoSuggest.prototype.itemMouseOut = function(oCurElement) { |
||
257 | oCurElement.className = 'auto_suggest_item'; |
||
258 | }; |
||
259 | |||
260 | /** |
||
261 | * Registers a callback function to be called when the auto-suggest results are available. |
||
262 | */ |
||
263 | elk_AutoSuggest.prototype.registerCallback = function(sCallbackType, sCallback) { |
||
264 | switch (sCallbackType) |
||
265 | { |
||
266 | case 'onBeforeAddItem': |
||
267 | this.oCallback.onBeforeAddItem = sCallback; |
||
268 | break; |
||
269 | |||
270 | case 'onAfterAddItem': |
||
271 | this.oCallback.onAfterAddItem = sCallback; |
||
272 | break; |
||
273 | |||
274 | case 'onAfterDeleteItem': |
||
275 | this.oCallback.onAfterDeleteItem = sCallback; |
||
276 | break; |
||
277 | |||
278 | case 'onBeforeUpdate': |
||
279 | this.oCallback.onBeforeUpdate = sCallback; |
||
280 | break; |
||
281 | } |
||
282 | }; |
||
283 | |||
284 | /** |
||
285 | * Handle form submission for the AutoSuggest component. |
||
286 | * |
||
287 | * @returns {boolean} - Returns false to prevent the default form submission behavior. |
||
288 | */ |
||
289 | elk_AutoSuggest.prototype.handleSubmit = function() { |
||
290 | let bReturnValue = true, |
||
291 | oFoundEntry = null; |
||
292 | |||
293 | // Do we have something that matches the current text? |
||
294 | for (let i = 0; i < this.aCache.length; i++) |
||
295 | { |
||
296 | if (this.sLastSearch.toLowerCase() === this.aCache[i].sItemName.toLowerCase().substring(0, this.sLastSearch.length)) |
||
297 | { |
||
298 | // Exact match? |
||
299 | if (this.sLastSearch.length === this.aCache[i].sItemName.length) |
||
300 | { |
||
301 | // This is the one! |
||
302 | oFoundEntry = { |
||
303 | sItemId: this.aCache[i].sItemId, |
||
304 | sItemName: this.aCache[i].sItemName |
||
305 | }; |
||
306 | |||
307 | break; |
||
308 | } |
||
309 | // Not an exact match, but it'll do for now. |
||
310 | else |
||
311 | { |
||
0 ignored issues
–
show
Comprehensibility
introduced
by
![]() |
|||
312 | // If we have two matches don't find anything. |
||
313 | if (oFoundEntry !== null) |
||
314 | { |
||
315 | bReturnValue = false; |
||
316 | } |
||
317 | else |
||
318 | { |
||
319 | oFoundEntry = { |
||
320 | sItemId: this.aCache[i].sItemId, |
||
321 | sItemName: this.aCache[i].sItemName |
||
322 | }; |
||
323 | } |
||
324 | } |
||
325 | } |
||
326 | } |
||
327 | |||
328 | if (oFoundEntry === null || bReturnValue === false) |
||
329 | { |
||
330 | return bReturnValue; |
||
331 | } |
||
332 | |||
333 | this.addItemLink(oFoundEntry.sItemId, oFoundEntry.sItemName, true); |
||
334 | return false; |
||
335 | }; |
||
336 | |||
337 | /** |
||
338 | * Positions the suggestion dropdown div based on the input element's position. |
||
339 | */ |
||
340 | elk_AutoSuggest.prototype.positionDiv = function() { |
||
341 | // Only do it once. |
||
342 | if (this.bPositionComplete) |
||
343 | { |
||
344 | return true; |
||
345 | } |
||
346 | |||
347 | this.bPositionComplete = true; |
||
348 | |||
349 | // Put the div under the text box. |
||
350 | let aParentPos = elk_itemPos(this.oTextHandle); |
||
351 | |||
352 | this.oSuggestDivHandle.style.left = aParentPos[0] + 'px'; |
||
353 | this.oSuggestDivHandle.style.top = (aParentPos[1] + this.oTextHandle.offsetHeight) + 'px'; |
||
354 | this.oSuggestDivHandle.style.width = this.oTextHandle.clientWidth + 'px'; |
||
355 | |||
356 | return true; |
||
357 | }; |
||
358 | |||
359 | /** |
||
360 | * Called when an item in the auto suggest list is clicked. |
||
361 | */ |
||
362 | elk_AutoSuggest.prototype.itemClicked = function(oEvent) { |
||
363 | let target = 'target' in oEvent ? oEvent.target : oEvent; |
||
364 | |||
365 | // Is there a div that we are populating? |
||
366 | if (this.bItemList) |
||
367 | { |
||
368 | this.addItemLink(target.sItemId, target.innerHTML, false); |
||
369 | } |
||
370 | // Otherwise clear things down. |
||
371 | else |
||
372 | { |
||
373 | this.oTextHandle.value = target.innerHTML.php_unhtmlspecialchars(); |
||
374 | } |
||
375 | |||
376 | this.oRealTextHandle.value = this.oTextHandle.value; |
||
377 | this.autoSuggestActualHide(); |
||
378 | this.oSelectedDiv = null; |
||
379 | }; |
||
380 | |||
381 | /** |
||
382 | * Removes the last entered search string from the auto-suggest component. |
||
383 | */ |
||
384 | elk_AutoSuggest.prototype.removeLastSearchString = function() { |
||
385 | // Remove the text we searched for from the div. |
||
386 | let sTempText = this.oTextHandle.value.toLowerCase(), |
||
387 | iStartString = sTempText.indexOf(this.sLastSearch.toLowerCase()); |
||
388 | |||
389 | // Just attempt to remove the bits we just searched for. |
||
390 | if (iStartString !== -1) |
||
391 | { |
||
392 | while (iStartString > 0) |
||
393 | { |
||
394 | if (sTempText.charAt(iStartString - 1) === '"' || sTempText.charAt(iStartString - 1) === ',' || sTempText.charAt(iStartString - 1) === ' ') |
||
395 | { |
||
396 | iStartString--; |
||
397 | if (sTempText.charAt(iStartString - 1) === ',') |
||
398 | { |
||
399 | break; |
||
400 | } |
||
401 | } |
||
402 | else |
||
403 | { |
||
404 | break; |
||
405 | } |
||
406 | } |
||
407 | |||
408 | // Now remove anything from iStartString upwards. |
||
409 | this.oTextHandle.value = this.oTextHandle.value.substring(0, iStartString); |
||
410 | } |
||
411 | // Just take it all. |
||
412 | else |
||
413 | { |
||
414 | this.oTextHandle.value = ''; |
||
415 | } |
||
416 | }; |
||
417 | |||
418 | /** |
||
419 | * Adds an item link to the item list. |
||
420 | * |
||
421 | * @param {string} sItemId - The ID of the item. |
||
422 | * @param {string} sItemName - The name of the item. |
||
423 | * @param {boolean} bFromSubmit - Specifies whether the call is from a submit action. |
||
424 | */ |
||
425 | elk_AutoSuggest.prototype.addItemLink = function(sItemId, sItemName, bFromSubmit) { |
||
426 | // Increase the internal item count. |
||
427 | this.iItemCount++; |
||
428 | |||
429 | // If there's a callback then call it. |
||
430 | if (typeof this.oCallback.onBeforeAddItem === 'function') |
||
431 | { |
||
432 | // If it returns false the item must not be added. |
||
433 | if (!this.oCallback.onBeforeAddItem.call(this, sItemId)) |
||
434 | { |
||
435 | return; |
||
436 | } |
||
437 | } |
||
438 | |||
439 | let oNewDiv = document.createElement('div'); |
||
440 | oNewDiv.id = 'suggest_' + this.opt.sSuggestId + '_' + sItemId; |
||
441 | oNewDiv.innerHTML = this.sItemTemplate |
||
442 | .replace(/%post_name%/g, this.opt.sPostName) |
||
443 | .replace(/%item_id%/g, sItemId) |
||
444 | .replace(/%item_href%/g, elk_prepareScriptUrl(elk_scripturl) + this.opt.sURLMask.replace(/%item_id%/g, sItemId)) |
||
445 | .replace(/%item_name%/g, sItemName) |
||
446 | .replace(/%images_url%/g, elk_images_url).replace(/%delete_text%/g, this.sTextDeleteItem); |
||
0 ignored issues
–
show
The variable
elk_images_url seems to be never declared. If this is a global, consider adding a /** global: elk_images_url */ comment.
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed. To learn more about declaring variables in Javascript, see the MDN. ![]() |
|||
447 | oNewDiv.getElementsByClassName('icon-small')[0].addEventListener('click', this.deleteAddedItem.bind(this)); |
||
448 | this.oItemList.appendChild(oNewDiv); |
||
449 | |||
450 | // If there's a registered callback, call it. |
||
451 | if (typeof this.oCallback.onAfterAddItem === 'function') |
||
452 | { |
||
453 | this.oCallback.onAfterAddItem.call(this, oNewDiv.id); |
||
454 | } |
||
455 | |||
456 | // Clear the div a bit. |
||
457 | this.removeLastSearchString(); |
||
458 | |||
459 | // If we came from a submit, and there's still more to go, turn on auto add for all the other things. |
||
460 | this.bDoAutoAdd = this.oTextHandle.value !== '' && bFromSubmit; |
||
461 | |||
462 | // Update the fellow. |
||
463 | this.autoSuggestUpdate(); |
||
464 | }; |
||
465 | |||
466 | /** |
||
467 | * Deletes the added item from the auto-suggest component. |
||
468 | */ |
||
469 | elk_AutoSuggest.prototype.deleteAddedItem = function(oEvent) { |
||
470 | let oDiv; |
||
471 | |||
472 | // A registerCallback, e.g. PM preventing duplicate entries |
||
473 | if (typeof oEvent === 'string') |
||
474 | { |
||
475 | oDiv = document.getElementById('suggest_' + this.opt.sSuggestId + '_' + oEvent); |
||
476 | } |
||
477 | |||
478 | // Or the remove button |
||
479 | if (typeof oEvent === 'object') |
||
480 | { |
||
481 | oDiv = oEvent.target.parentNode; |
||
482 | } |
||
483 | |||
484 | // Remove the div if it exists. |
||
485 | if (oDiv !== null) |
||
0 ignored issues
–
show
|
|||
486 | { |
||
487 | this.oItemList.removeChild(oDiv); |
||
488 | |||
489 | // Decrease the internal item count. |
||
490 | this.iItemCount--; |
||
491 | |||
492 | // If there's a registered callback, call it. |
||
493 | if (typeof this.oCallback.onAfterDeleteItem === 'function') |
||
494 | { |
||
495 | this.oCallback.onAfterDeleteItem.call(this); |
||
496 | } |
||
497 | } |
||
498 | |||
499 | return false; |
||
500 | }; |
||
501 | |||
502 | /** |
||
503 | * Hide the auto suggest suggestions. |
||
504 | */ |
||
505 | elk_AutoSuggest.prototype.autoSuggestHide = function() { |
||
506 | // Delay to allow events to propagate through.... |
||
507 | this.oHideTimer = setTimeout(this.autoSuggestActualHide.bind(this), 350); |
||
508 | }; |
||
509 | |||
510 | /** |
||
511 | * Hides the actual auto-suggest dropdown after a timeout |
||
512 | */ |
||
513 | elk_AutoSuggest.prototype.autoSuggestActualHide = function() { |
||
514 | this.oSuggestDivHandle.style.display = 'none'; |
||
515 | this.oSuggestDivHandle.style.visibility = 'hidden'; |
||
516 | this.oSelectedDiv = null; |
||
517 | }; |
||
518 | |||
519 | /** |
||
520 | * Shows the auto suggest dropdown when triggered by the user. |
||
521 | */ |
||
522 | elk_AutoSuggest.prototype.autoSuggestShow = function() { |
||
523 | if (this.oHideTimer) |
||
524 | { |
||
525 | clearTimeout(this.oHideTimer); |
||
526 | this.oHideTimer = false; |
||
527 | } |
||
528 | |||
529 | this.positionDiv(); |
||
530 | this.oSuggestDivHandle.style.visibility = 'visible'; |
||
531 | this.oSuggestDivHandle.style.display = ''; |
||
532 | }; |
||
533 | |||
534 | /** |
||
535 | * Populates the auto-suggest dropdown div with suggestions based on the provided data. |
||
536 | */ |
||
537 | elk_AutoSuggest.prototype.populateDiv = function(aResults) { |
||
538 | // Cannot have any children yet. |
||
539 | while (this.oSuggestDivHandle.childNodes.length > 0) |
||
540 | { |
||
541 | // Tidy up the events etc. too. |
||
542 | this.oSuggestDivHandle.childNodes[0].onmouseover = null; |
||
543 | this.oSuggestDivHandle.childNodes[0].onmouseout = null; |
||
544 | this.oSuggestDivHandle.childNodes[0].onclick = null; |
||
545 | |||
546 | this.oSuggestDivHandle.removeChild(this.oSuggestDivHandle.childNodes[0]); |
||
547 | } |
||
548 | |||
549 | // Something to display? |
||
550 | if (typeof aResults === 'undefined') |
||
551 | { |
||
552 | this.aDisplayData = []; |
||
553 | return false; |
||
554 | } |
||
555 | |||
556 | let aNewDisplayData = []; |
||
557 | for (let i = 0; i < (aResults.length > this.iMaxDisplayQuantity ? this.iMaxDisplayQuantity : aResults.length); i++) |
||
558 | { |
||
559 | // Create the sub element |
||
560 | let oNewDivHandle = document.createElement('div'); |
||
561 | |||
562 | oNewDivHandle.sItemId = aResults[i].sItemId; |
||
563 | oNewDivHandle.className = 'auto_suggest_item'; |
||
564 | oNewDivHandle.innerHTML = aResults[i].sItemName; |
||
565 | //oNewDivHandle.style.width = this.oTextHandle.style.width; |
||
566 | |||
567 | this.oSuggestDivHandle.appendChild(oNewDivHandle); |
||
568 | |||
569 | // Attach some events to it, so we can do stuff. |
||
570 | oNewDivHandle.onmouseover = function() { |
||
571 | this.className = 'auto_suggest_item_hover'; |
||
572 | }; |
||
573 | oNewDivHandle.onmouseout = function() { |
||
574 | this.className = 'auto_suggest_item'; |
||
575 | }; |
||
576 | oNewDivHandle.onclick = this.itemClicked.bind(this); |
||
577 | |||
578 | aNewDisplayData[i] = oNewDivHandle; |
||
579 | } |
||
580 | |||
581 | this.aDisplayData = aNewDisplayData; |
||
582 | |||
583 | return true; |
||
584 | }; |
||
585 | |||
586 | /** |
||
587 | * Callback function for the XML request, should contain the list of users that match |
||
588 | */ |
||
589 | elk_AutoSuggest.prototype.onSuggestionReceived = function(oXMLDoc) { |
||
590 | if (oXMLDoc === false) |
||
591 | { |
||
592 | return; |
||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||
593 | } |
||
594 | |||
595 | let aItems = oXMLDoc.getElementsByTagName('item'); |
||
596 | |||
597 | // Go through each item received |
||
598 | this.aCache = []; |
||
599 | for (let i = 0; i < aItems.length; i++) |
||
600 | { |
||
601 | this.aCache[i] = { |
||
602 | sItemId: aItems[i].getAttribute('id'), |
||
603 | sItemName: aItems[i].childNodes[0].nodeValue |
||
604 | }; |
||
605 | |||
606 | // If we're doing auto add, and we found the exact person, then add them! |
||
607 | if (this.bDoAutoAdd && this.sLastSearch === this.aCache[i].sItemName) |
||
608 | { |
||
609 | let oReturnValue = { |
||
610 | sItemId: this.aCache[i].sItemId, |
||
611 | sItemName: this.aCache[i].sItemName |
||
612 | }; |
||
613 | |||
614 | this.aCache = []; |
||
615 | return this.addItemLink(oReturnValue.sItemId, oReturnValue.sItemName, true); |
||
616 | } |
||
617 | } |
||
618 | |||
619 | // Check we don't try to keep auto updating! |
||
620 | this.bDoAutoAdd = false; |
||
621 | |||
622 | // Populate the div. |
||
623 | this.populateDiv(this.aCache); |
||
624 | |||
625 | // Make sure we can see it - if we can. |
||
626 | if (aItems.length === 0) |
||
627 | { |
||
628 | this.autoSuggestHide(); |
||
629 | } |
||
630 | else |
||
631 | { |
||
632 | this.autoSuggestShow(); |
||
633 | } |
||
634 | |||
635 | return true; |
||
636 | }; |
||
637 | |||
638 | /** |
||
639 | * Update the suggestions in the auto suggest dropdown based on the user input |
||
640 | */ |
||
641 | elk_AutoSuggest.prototype.autoSuggestUpdate = function() { |
||
642 | // If there's a callback then call it. |
||
643 | if (typeof this.oCallback.onBeforeUpdate === 'function') |
||
644 | { |
||
645 | // If it returns false the item must not be added. |
||
646 | if (!this.oCallback.onBeforeUpdate.call(this)) |
||
647 | { |
||
648 | return false; |
||
649 | } |
||
650 | } |
||
651 | |||
652 | this.oRealTextHandle.value = this.oTextHandle.value; |
||
653 | |||
654 | if (isEmptyText(this.oTextHandle)) |
||
655 | { |
||
656 | this.aCache = []; |
||
657 | this.populateDiv(); |
||
658 | this.autoSuggestHide(); |
||
659 | |||
660 | return true; |
||
661 | } |
||
662 | |||
663 | // Nothing changed? |
||
664 | if (this.oTextHandle.value === this.sLastDirtySearch) |
||
665 | { |
||
666 | return true; |
||
667 | } |
||
668 | this.sLastDirtySearch = this.oTextHandle.value; |
||
669 | |||
670 | // We're only actually interested in the last string. |
||
671 | let sSearchString = this.oTextHandle.value.replace(/^("[^"]+",[ ]*)+/, '').replace(/^([^,]+,[ ]*)+/, ''); |
||
672 | if (sSearchString.substring(0, 1) === '"') |
||
673 | { |
||
674 | sSearchString = sSearchString.substring(1); |
||
675 | } |
||
676 | |||
677 | // Stop replication ASAP. |
||
678 | let sRealLastSearch = this.sLastSearch; |
||
679 | this.sLastSearch = sSearchString; |
||
680 | |||
681 | // Either nothing or we've completed a sentence. |
||
682 | if (sSearchString === '' || sSearchString.substring(sSearchString.length - 1) === '"') |
||
683 | { |
||
684 | this.populateDiv(); |
||
685 | return true; |
||
686 | } |
||
687 | |||
688 | // Nothing? |
||
689 | if (sRealLastSearch === sSearchString) |
||
690 | { |
||
691 | return true; |
||
692 | } |
||
693 | |||
694 | // Too small? |
||
695 | if (sSearchString.length < this.iMinimumSearchChars) |
||
696 | { |
||
697 | this.aCache = []; |
||
698 | this.autoSuggestHide(); |
||
699 | return true; |
||
700 | } |
||
701 | |||
702 | if (sSearchString.substring(0, sRealLastSearch.length) === sRealLastSearch) |
||
703 | { |
||
704 | // Instead of hitting the server again, just narrow down the results... |
||
705 | let aNewCache = [], |
||
706 | j = 0, |
||
707 | sLowercaseSearch = sSearchString.toLowerCase(); |
||
708 | |||
709 | for (let k = 0; k < this.aCache.length; k++) |
||
710 | { |
||
711 | if (this.aCache[k].sItemName.slice(0, sSearchString.length).toLowerCase() === sLowercaseSearch) |
||
712 | { |
||
713 | aNewCache[j++] = this.aCache[k]; |
||
714 | } |
||
715 | } |
||
716 | |||
717 | this.aCache = []; |
||
718 | if (aNewCache.length !== 0) |
||
719 | { |
||
720 | this.aCache = aNewCache; |
||
721 | |||
722 | // Repopulate. |
||
723 | this.populateDiv(this.aCache); |
||
724 | |||
725 | // Check it can be seen. |
||
726 | this.autoSuggestShow(); |
||
727 | |||
728 | return true; |
||
729 | } |
||
730 | } |
||
731 | |||
732 | // In progress means destroy! |
||
733 | if (typeof this.oXmlRequestHandle === 'object' && this.oXmlRequestHandle !== null) |
||
734 | { |
||
735 | this.oXmlRequestHandle.abort(); |
||
736 | } |
||
737 | |||
738 | // Clean the text handle. |
||
739 | sSearchString = sSearchString.php_urlencode(); |
||
740 | |||
741 | // Get the document. |
||
742 | let obj = { |
||
743 | 'suggest_type': this.opt.sSearchType, |
||
744 | 'search': sSearchString, |
||
745 | 'time': new Date().getTime() |
||
746 | }, |
||
747 | postString; |
||
748 | |||
749 | // Post values plus session |
||
750 | postString = serialize(obj) + '&' + this.opt.sSessionVar + '=' + this.opt.sSessionId; |
||
751 | |||
752 | sendXMLDocument.call(this, this.sRetrieveURL.replace(/%scripturl%/g, elk_prepareScriptUrl(elk_scripturl)), postString, this.onSuggestionReceived); |
||
0 ignored issues
–
show
The variable
sendXMLDocument seems to be never declared. If this is a global, consider adding a /** global: sendXMLDocument */ comment.
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed. To learn more about declaring variables in Javascript, see the MDN. ![]() |
|||
753 | |||
754 | return true; |
||
755 | }; |
||
756 |