mirror of
https://github.com/netbymatt/ws4kp.git
synced 2026-04-18 17:49:31 -07:00
non-jquery autocomplete, needs more keyboard integration
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
import { round2 } from './modules/utils/units.mjs';
|
||||
import { parseQueryString } from './modules/share.mjs';
|
||||
import settings from './modules/settings.mjs';
|
||||
import AutoComplete from './modules/autocomplete.mjs';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
init();
|
||||
@@ -56,7 +57,7 @@ const init = () => {
|
||||
document.addEventListener('keydown', documentKeydown);
|
||||
document.addEventListener('touchmove', (e) => { if (document.fullscreenElement) e.preventDefault(); });
|
||||
|
||||
$(TXT_ADDRESS_SELECTOR).devbridgeAutocomplete({
|
||||
const autoComplete = new AutoComplete(document.querySelector(TXT_ADDRESS_SELECTOR), {
|
||||
serviceUrl: 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/suggest',
|
||||
deferRequestBy: 300,
|
||||
paramName: 'text',
|
||||
@@ -76,13 +77,12 @@ const init = () => {
|
||||
minChars: 3,
|
||||
showNoSuggestionNotice: true,
|
||||
noSuggestionNotice: 'No results found. Please try a different search string.',
|
||||
onSelect(suggestion) { autocompleteOnSelect(suggestion, this); },
|
||||
onSelect(suggestion) { autocompleteOnSelect(suggestion); },
|
||||
width: 490,
|
||||
});
|
||||
|
||||
const formSubmit = () => {
|
||||
const ac = $(TXT_ADDRESS_SELECTOR).devbridgeAutocomplete();
|
||||
if (ac.suggestions[0]) $(ac.suggestionsContainer.children[0]).trigger('click');
|
||||
if (autoComplete.suggestions[0]) autoComplete.suggestionsContainer.children[0].trigger('click');
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -133,10 +133,7 @@ const init = () => {
|
||||
document.querySelector('#container').addEventListener('swiped-right', () => swipeCallBack('right'));
|
||||
};
|
||||
|
||||
const autocompleteOnSelect = async (suggestion, elem) => {
|
||||
// Do not auto get the same city twice.
|
||||
if (elem.previousSuggestionValue === suggestion.value) return;
|
||||
|
||||
const autocompleteOnSelect = async (suggestion) => {
|
||||
const data = await json('https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find', {
|
||||
data: {
|
||||
text: suggestion.value,
|
||||
|
||||
229
server/scripts/modules/autocomplete.mjs
Normal file
229
server/scripts/modules/autocomplete.mjs
Normal file
@@ -0,0 +1,229 @@
|
||||
/* eslint-disable default-case */
|
||||
import { json } from './utils/fetch.mjs';
|
||||
|
||||
const KEYS = {
|
||||
ESC: 27,
|
||||
TAB: 9,
|
||||
RETURN: 13,
|
||||
LEFT: 37,
|
||||
UP: 38,
|
||||
RIGHT: 39,
|
||||
DOWN: 40,
|
||||
};
|
||||
|
||||
const DEFAULT_OPTIONS = {
|
||||
autoSelectFirst: false,
|
||||
serviceUrl: null,
|
||||
lookup: null,
|
||||
onSelect: null,
|
||||
onHint: null,
|
||||
width: 'auto',
|
||||
minChars: 1,
|
||||
maxHeight: 300,
|
||||
deferRequestBy: 0,
|
||||
params: {},
|
||||
delimiter: null,
|
||||
zIndex: 9999,
|
||||
type: 'GET',
|
||||
noCache: false,
|
||||
preserveInput: false,
|
||||
containerClass: 'autocomplete-suggestions',
|
||||
tabDisabled: false,
|
||||
dataType: 'text',
|
||||
currentRequest: null,
|
||||
triggerSelectOnValidInput: true,
|
||||
preventBadQueries: true,
|
||||
paramName: 'query',
|
||||
transformResult: (a) => a,
|
||||
showNoSuggestionNotice: false,
|
||||
noSuggestionNotice: 'No results',
|
||||
orientation: 'bottom',
|
||||
forceFixPosition: false,
|
||||
};
|
||||
|
||||
const escapeRegExChars = (string) => string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
|
||||
const formatResult = (suggestion, search) => {
|
||||
// Do not replace anything if the current value is empty
|
||||
if (!search) {
|
||||
return suggestion;
|
||||
}
|
||||
|
||||
const pattern = `(${escapeRegExChars(search)})`;
|
||||
|
||||
return suggestion
|
||||
.replace(new RegExp(pattern, 'gi'), '<strong>$1</strong>')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/<(\/?strong)>/g, '<$1>');
|
||||
};
|
||||
|
||||
class AutoComplete {
|
||||
constructor(elem, options) {
|
||||
this.options = { ...DEFAULT_OPTIONS, ...options };
|
||||
this.elem = elem;
|
||||
this.selectedItem = -1;
|
||||
this.onChangeTimeout = null;
|
||||
this.currentValue = '';
|
||||
this.suggestions = [];
|
||||
this.cachedResponses = {};
|
||||
|
||||
// create and add the results container
|
||||
const results = document.createElement('div');
|
||||
results.style.display = 'none';
|
||||
results.classList.add(this.options.containerClass);
|
||||
results.style.width = (typeof this.options.width === 'string') ? this.options.width : `${this.options.width}px`;
|
||||
results.style.zIndex = this.options.zIndex;
|
||||
results.style.maxHeight = `${this.options.maxHeight}px`;
|
||||
results.style.overflowX = 'hidden';
|
||||
results.addEventListener('mouseover', (e) => this.mouseOver(e));
|
||||
results.addEventListener('mouseout', (e) => this.mouseOut(e));
|
||||
results.addEventListener('click', (e) => this.click(e));
|
||||
|
||||
this.results = results;
|
||||
this.elem.after(results);
|
||||
|
||||
// add handlers for typing text
|
||||
this.elem.addEventListener('keyup', (e) => this.keyUp(e));
|
||||
}
|
||||
|
||||
mouseOver(e) {
|
||||
// suggestion line
|
||||
if (e.target?.classList?.contains('suggestion')) {
|
||||
e.target.classList.add('selected');
|
||||
this.selectedItem = parseInt(e.target.dataset.item, 10);
|
||||
}
|
||||
}
|
||||
|
||||
mouseOut(e) {
|
||||
// suggestion line
|
||||
if (e.target?.classList?.contains('suggestion')) {
|
||||
e.target.classList.remove('selected');
|
||||
this.selectedItem = -1;
|
||||
}
|
||||
}
|
||||
|
||||
click(e) {
|
||||
// suggestion line
|
||||
if (e.target?.classList?.contains('suggestion')) {
|
||||
// get the entire suggestion
|
||||
const suggestion = this.suggestions[parseInt(e.target.dataset.item, 10)];
|
||||
this.options.onSelect(suggestion);
|
||||
this.elem.value = suggestion.value;
|
||||
this.hideSuggestions();
|
||||
}
|
||||
}
|
||||
|
||||
hideSuggestions() {
|
||||
this.results.style.display = 'none';
|
||||
}
|
||||
|
||||
showSuggestions() {
|
||||
this.results.style.removeProperty('display');
|
||||
}
|
||||
|
||||
clearSuggestions() {
|
||||
this.results.innerHTML = '';
|
||||
}
|
||||
|
||||
keyUp(e) {
|
||||
// ignore some keys
|
||||
switch (e.which) {
|
||||
case KEYS.UP:
|
||||
case KEYS.DOWN:
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(this.onChangeTimeout);
|
||||
|
||||
if (this.currentValue !== this.elem.value) {
|
||||
if (this.options.deferRequestBy > 0) {
|
||||
// defer lookup during rapid key presses
|
||||
this.onChangeTimeout = setTimeout(() => {
|
||||
this.onValueChange();
|
||||
}, this.options.deferRequestBy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onValueChange() {
|
||||
clearTimeout(this.onValueChange);
|
||||
|
||||
// confirm value actually changed
|
||||
if (this.currentValue === this.elem.value) return;
|
||||
// store new value
|
||||
this.currentValue = this.elem.value;
|
||||
|
||||
// clear the selected index
|
||||
this.selectedItem = -1;
|
||||
this.results.querySelectorAll('div').forEach((elem) => elem.classList.remove('selected'));
|
||||
|
||||
// if less than minimum don't query api
|
||||
if (this.currentValue.length < this.options.minChars) {
|
||||
this.hideSuggestions();
|
||||
return;
|
||||
}
|
||||
|
||||
this.getSuggestions(this.currentValue);
|
||||
}
|
||||
|
||||
async getSuggestions(search) {
|
||||
// assemble options
|
||||
const searchOptions = { ...this.options.params };
|
||||
searchOptions[this.options.paramName] = search;
|
||||
|
||||
// build search url
|
||||
const url = new URL(this.options.serviceUrl);
|
||||
Object.entries(searchOptions).forEach(([key, value]) => {
|
||||
url.searchParams.append(key, value);
|
||||
});
|
||||
|
||||
let result = this.cachedResponses[search];
|
||||
if (!result) {
|
||||
// make the request
|
||||
const resultRaw = await json(url);
|
||||
|
||||
// use the provided parser
|
||||
result = this.options.transformResult(resultRaw);
|
||||
}
|
||||
|
||||
// store suggestions
|
||||
this.cachedResponses[search] = result.suggestions;
|
||||
this.suggestions = result.suggestions;
|
||||
|
||||
// populate the suggestion area
|
||||
this.populateSuggestions();
|
||||
}
|
||||
|
||||
populateSuggestions() {
|
||||
if (this.suggestions.length === 0) {
|
||||
if (this.options.showNoSuggestionNotice) {
|
||||
this.noSuggestionNotice();
|
||||
} else {
|
||||
this.hideSuggestions();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// build the list
|
||||
const suggestionElems = this.suggestions.map((suggested, idx) => {
|
||||
const elem = document.createElement('div');
|
||||
elem.classList.add('suggestion');
|
||||
elem.dataset.item = idx;
|
||||
elem.innerHTML = (formatResult(suggested.value, this.currentValue));
|
||||
return elem.outerHTML;
|
||||
});
|
||||
|
||||
this.results.innerHTML = suggestionElems.join('');
|
||||
this.showSuggestions();
|
||||
}
|
||||
|
||||
noSuggestionNotice() {
|
||||
this.results.innerHTML = `<div>${this.options.noSuggestionNotice}</div>`;
|
||||
this.showSuggestions();
|
||||
}
|
||||
}
|
||||
|
||||
export default AutoComplete;
|
||||
10716
server/scripts/vendor/auto/jquery.js
vendored
10716
server/scripts/vendor/auto/jquery.js
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -106,23 +106,26 @@ body {
|
||||
.autocomplete-suggestions {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #000000;
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.autocomplete-suggestion {
|
||||
div {
|
||||
/*padding: 2px 5px;*/
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 16pt;
|
||||
|
||||
&.selected {
|
||||
background-color: #0000ff;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.autocomplete-selected {
|
||||
background-color: #0000ff;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
#divTwc {
|
||||
|
||||
Reference in New Issue
Block a user