Last active
December 14, 2017 12:47
-
-
Save DotNetNerd/20428a622d5195e08391a0a546666259 to your computer and use it in GitHub Desktop.
Type(Script)Ahead
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface ITypeaheadArgs | |
{ | |
minChars?: number, | |
onSelect?: () => void | |
} | |
async function typeahead( | |
field: HTMLInputElement, | |
search: (text: string) => Promise<string[]>, | |
args: ITypeaheadArgs) { | |
let handleKey = (event: KeyboardEvent, keyCode: number, | |
eventHandler: (typeahead: HTMLUListElement) => void) => { | |
if (event.keyCode === keyCode) { | |
event.preventDefault(); | |
event.stopImmediatePropagation(); | |
if (field.parentElement) { | |
let typeahead = <HTMLUListElement>field.parentElement.querySelector('.js-typeahead'); | |
if (typeahead) { | |
eventHandler(typeahead); | |
} | |
} | |
} | |
}; | |
field.addEventListener('keydown', event => { | |
if (event.keyCode === 38 || event.keyCode === 40) { | |
event.preventDefault(); | |
} | |
}); | |
field.addEventListener('keyup', event => { | |
if (!field.parentElement) { return; } | |
handleKey(event, 38, typeahead => { | |
let selected = typeahead.querySelector('.selected'); | |
if (selected) { | |
let prev = selected.previousElementSibling; | |
while (prev !== null && prev.nodeType === Node.TEXT_NODE) { | |
prev = selected.previousElementSibling; | |
} | |
if (prev) { | |
selected.classList.remove('selected'); | |
prev.classList.add('selected'); | |
} | |
} | |
}); | |
handleKey(event, 40, typeahead => { | |
let selected = typeahead.querySelector('.selected'); | |
if (selected) { | |
let next = selected.nextElementSibling; | |
while (next !== null && next.nodeType === Node.TEXT_NODE) { | |
next = selected.nextElementSibling; | |
} | |
if (next) { | |
selected.classList.remove('selected'); | |
next.classList.add('selected'); | |
} | |
} else { | |
let first = typeahead.querySelector('li'); | |
if (first) { | |
first.classList.add('selected'); | |
} | |
} | |
}); | |
handleKey(event, 13, typeahead => { | |
let selected = typeahead.querySelector('.selected'); | |
if (selected) { | |
field.value = selected.innerHTML; | |
typeahead.innerHTML = ''; | |
if (args.onSelect) { | |
args.onSelect(); | |
} | |
} | |
}); | |
}); | |
field.addEventListener('keyup', debounce(async () => { | |
if (field.parentElement) { | |
let typeahead = | |
<HTMLUListElement>field.parentElement.querySelector('.js-typeahead'); | |
if (!typeahead) { | |
typeahead = document.createElement('ul'); | |
typeahead.classList.add('js-typeahead') | |
if (field.parentNode) { | |
field.parentNode.insertBefore(typeahead, field.nextSibling); | |
} | |
addChildClickListener(typeahead, 'li', e => { | |
field.value = e.innerHTML; | |
typeahead.innerHTML = ''; | |
if (args.onSelect) { | |
args.onSelect(); | |
} | |
}); | |
document.body.addEventListener('click', () => typeahead.innerHTML = ''); | |
} | |
if ((!args.minChars | |
|| field.value.trim().length >= args.minChars) && field.parentElement) { | |
let items = await search(field.value); | |
if (items) { | |
let liReducer = | |
(acc: string, val: string): string => acc += `<li>${val}</li>`; | |
typeahead.innerHTML = items.reduce(liReducer, ''); | |
} else { | |
typeahead.innerHTML = ''; | |
} | |
} else { | |
typeahead.innerHTML = ''; | |
} | |
} | |
}, 500)); | |
} | |
function addChildClickListener(node: HTMLElement, | |
selector: string, f: (e: HTMLElement) => any) { | |
node.addEventListener('click', (e: MouseEvent) => { | |
e.preventDefault(); | |
if(e.srcElement && e.srcElement.matches(selector)) { | |
f(<HTMLElement>e.srcElement); | |
} | |
}); | |
} | |
function debounce(func: () => void, wait = 100) { | |
let h: number; | |
return () => { | |
clearTimeout(h); | |
h = setTimeout(() => func(), wait); | |
}; | |
} | |
/* | |
CSS: | |
.js-typeahead { | |
z-index: 99; | |
position: absolute; | |
background-color: white; | |
border: solid 1px lightgrey; | |
padding: 0; | |
list-style-type: none; | |
border-top: 0; | |
} | |
.js-typeahead li { | |
padding: 5px; | |
cursor: pointer; | |
} | |
.js-typeahead li.selected { | |
background-color: lightblue; | |
} | |
*/ | |
// Use like: | |
// let element = document.getElementById('myBox'); | |
// typeahead(element, | |
// searchText => await someClient.Startswith(searchText), | |
// {minChars: 5, onSelect: () => alert(`${element.value} selected`);}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment