<div class="wrapper">
<div class="container">
<img class="logo" src="https://ravennainteractive.com/assets/img/ravenna.svg">
<div class="typeahead-wrapper">
<div id="typeahead-container">
<label for="typeahed">Search by movie title or lead actor</label>
<input id="typeahead-input" name="typeahead" required>
<div id="typeahead-results" data-selector="results">
</div>
<div id="typeahead-selection">
<p></p>
</div>
</div>
</div>
</div>
</div>
html, body {
height: 100%;
background: black;
color: white;
font-family: sans-serif;
}
.wrapper {
height: 100%;
padding: 1rem;
}
.container {
display: flex;
flex-direction: column;
height: 100%;
gap: 1rem;
}
.logo {
max-width: 100%;
width: 12rem;
}
.typeahead-wrapper {
display: flex;
flex: 1;
gap: 1rem;
}
#typeahead-container {
display: flex;
flex: 1;
flex-direction: column;
}
#typeahead-input {
margin-top: .5rem;
padding: .5rem 1rem;
box-shadow: 1.5px 2px 0px 1px #5ce8a4;
border-radius: 5px;
border: none;
max-width: 40rem;
}
#typeahead-results {
opacity: 0;
max-height: 0;
margin-top: 1rem;
transition: max-height ease-in-out .2s;
}
#typeahead-results button {
display: block;
width: 100%;
text-align: left;
background: #fff;
border-color: #5ce8a4;
padding: 1rem;
}
#typeahead-results button:not(:first-child) {
margin-top: .5rem;
}
#typeahead-results.show {
opacity: 1;
max-height: 5rem;
}
function TypeAhead(rootId, handleSelect) {
const root = document.getElementById(rootId);
const input = root.querySelector('input');
const resultsContainer = root.querySelector('[data-selector="results"]');
const selectionContainer = root.querySelector('#typeahead-selection');
// Placeholder for creating these els if they don't exist
const search = async () => {
selectionContainer.innerHTML = '';
const val = input.value;
if (val.length > 2) {
const res = await fetch(`https://ravennainteractive.com/codepen/typeahead?query=${val}`);
const data = await res.json();
console.log(data)
if (!res.ok) {
console.log(data);
return;
}
return data;
}
}
const displayResults = (results) => {
resultsContainer.innerHTML = "";
if (!results || !results.length) {
selectionContainer.innerHTML = "<p>No results found. Try searching for Tom</p>";
return;
}
const resultsFrag = document.createDocumentFragment();
results.forEach((result, i) => {
resultsFrag.appendChild(buildResult({result, index: i}));
})
resultsContainer.appendChild(resultsFrag);
}
const buildResult = ({result, index}) => {
const button = document.createElement('button');
button.dataset.index = index;
button.dataset.value = result.id;
button.innerHTML = `<div>Title: ${result.title}</div><div>Lead: ${result.starring_actor}</div>`
button.dataset.identifier = 'result-item';
button.addEventListener('click', e => {
input.value = result.title;
handleSelect(e);
resultsContainer.classList.remove('show');
selectionContainer.innerHTML = `<p>You picked ${result.title} starring ${result.starring_actor}! You are probably gonna do something really cool with the result.</p>`
});
/*
div.addEventListener('keyup', e => {
if (e.key === 'Enter') {
input.value = result.label;
handleSelect(e);
resultsContainer.classList.remove('show')
}
})
*/
return button;
}
const handleArrowUp = (e) => {
e.stopPropagation();
const resultItems = [resultsContainer.children];
if (!resultItems.length) return;
const active = document.activeElement;
const activeIndex = resultItems.indexOf(active);
if (activeIndex < 1) {
input.focus();
} else {
resultItems[activeIndex - 1].focus();
}
}
const handleArrowDown = (e) => {
e.stopPropagation();
const resultItems = [resultsContainer.children];
if (!resultItems.length) return;
const active = document.activeElement;
const activeIndex = resultItems.indexOf(active);
resultItems[Math.min(activeIndex + 1, resultItems.length - 1)].focus();
}
root.addEventListener('keyup', e => {
switch (e.key) {
case 'ArrowUp':
handleArrowUp(e)
break;
case 'ArrowDown':
handleArrowDown(e)
break;
}
});
root.addEventListener('click', e => e.stopPropagation())
window.addEventListener('click', e => {
resultsContainer.classList.remove('show');
})
input.addEventListener('focus', e => {
resultsContainer.classList.add('show');
})
input.addEventListener('input', debounce(async () => {
const items = await search();
displayResults(items);
}))
}
// Standard debounce function
const debounce = (func, delay = 300) => {
let timer;
return (args) => {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
clearTimeout(timer);
}, delay)
}
}
TypeAhead('typeahead-container', (result) => console.log(result))
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.